diff --git a/FILES.json b/FILES.json index 0abb210..d4f8e57 100644 --- a/FILES.json +++ b/FILES.json @@ -32,7 +32,7 @@ }, { "id": "core", - "patterns": ["js/version.js", "js/event_bus.js", "js/registry_base.js", "js/disease_registry.js", "js/sound_pack.js", "js/data.js", "js/ground_types.js", "js/health.js", "js/input_mode_manager.js", "js/main.js", "js/math.js", "js/patch_helpers.js", "js/perf_profiler.js", "js/debug_tools.js"], + "patterns": ["js/version.js", "js/event_bus.js", "js/registry_base.js", "js/disease_registry.js", "js/sound_pack.js", "js/data.js", "js/ground_types.js", "js/health.js", "js/input_mode_manager.js", "js/main.js", "js/math.js", "js/perf_profiler.js", "js/debug_tools.js"], "meaning": "Bootstrap, shared constants/helpers, registries, config, profiling/debug." }, { diff --git a/app_manifest.json b/app_manifest.json index 46c3a39..89c1eab 100644 --- a/app_manifest.json +++ b/app_manifest.json @@ -1,5 +1,5 @@ { - "version": "39.15.63", + "version": "39.15.65", "css": [ "css/base.css", "css/layout.css", @@ -25,7 +25,6 @@ "js/math.js", "js/collision_footprint_system.js", "js/physics_helpers.js", - "js/placement_collision_system.js", "js/placement_preview_system.js", "js/pin_attachment_system.js", "js/physics_shape_editor_system.js", @@ -44,7 +43,6 @@ "js/items.js", "js/item_type_initializers.js", "js/item_lifecycle_support.js", - "js/item_lifecycle_runtime.js", "js/item_update_policy.js", "js/item_dynamic_tool_system.js", "js/item_dynamic_ball_system.js", @@ -81,8 +79,6 @@ "js/tarinai_building_behavior.js", "js/tarinai_action_definitions.js", "js/tarinai_needs_items.js", - "js/tarinai_forced_action_planner_system.js", - "js/tarinai_action_planner_system.js", "js/tarinai_need_planner_system.js", "js/tarinai_item_interaction_context.js", "js/tarinai_food_interaction_system.js", @@ -122,7 +118,6 @@ "js/simulation_maintenance_system.js", "js/simulation_ambient_system.js", "js/simulation_systems.js", - "js/world_update_phases.js", "js/system_order.js", "js/simulation.js", "js/colony_situation_system.js", @@ -143,7 +138,6 @@ "js/save_codec.js", "js/save_storage.js", "js/save_system.js", - "js/patch_helpers.js", "js/ui_tooltips.js", "js/ui_layout_dialogs.js", "js/ui_ground.js", diff --git a/assets/ui/favicon-64.png b/assets/ui/favicon-64.png deleted file mode 100644 index 92594d7..0000000 Binary files a/assets/ui/favicon-64.png and /dev/null differ diff --git a/assets/ui/tool_water_bowl.webp b/assets/ui/tool_water_bowl.webp deleted file mode 100644 index 3dc7384..0000000 Binary files a/assets/ui/tool_water_bowl.webp and /dev/null differ diff --git a/css/base.css b/css/base.css index ea7c7e6..d07ed88 100644 --- a/css/base.css +++ b/css/base.css @@ -74,25 +74,6 @@ button { line-height: 1.55; min-height: 1.55em; } -.loading-bar { - height: 12px; - margin: 18px 0 8px; - border-radius: 999px; - overflow: hidden; - background: rgba(90, 70, 48, 0.12); -} -.loading-bar-fill { - width: 0%; - height: 100%; - border-radius: inherit; - background: linear-gradient(90deg, #9ce84b, #f0d35b); - transition: width 160ms ease; -} -.loading-percent { - color: var(--muted); - font-size: 12px; - font-variant-numeric: tabular-nums; -} #app { width: min(1480px, 100vw); @@ -137,13 +118,6 @@ h1 { white-space: nowrap; } -.subtitle { - margin: 0; - color: var(--muted); - font-size: clamp(11px, 1.1vw, 13px); - line-height: 1.35; - white-space: nowrap; -} .top-actions { display: flex; @@ -429,7 +403,6 @@ h1 { z-index: 1; filter: drop-shadow(0 2px 3px rgba(52,40,26,0.12)); } -.tool-sprite-watermark { display: none !important; } .tool[data-tool="giant_drug"] .tool-sprite-preview { width: 39px; height: 39px; left: -4px; } .tool[data-tool="dwarf_drug"] .tool-sprite-preview, .tool[data-tool="oshibyo"] .tool-sprite-preview { width: 22px; height: 22px; left: 5px; } diff --git a/css/components.css b/css/components.css index 78c560e..7a20e67 100644 --- a/css/components.css +++ b/css/components.css @@ -246,12 +246,6 @@ font-size: 12px; color: rgba(88, 73, 60, 0.82); } -.loading-hint { - margin: 10px 0 0; - font-size: 12px; - line-height: 1.55; - color: rgba(111, 92, 73, 0.78); -} .collapsible-card { padding-top: 10px; diff --git a/css/layout.css b/css/layout.css index 44ec8d3..c5eb8dd 100644 --- a/css/layout.css +++ b/css/layout.css @@ -74,8 +74,6 @@ margin: 10px 0 0; } -.tool-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; } -.tool-head h2 { margin-bottom: 0; } .btn.mini { padding: 6px 10px; font-size: 12px; } .tool-body { margin-top: 10px; } .tool-palette { display: flex; flex-direction: column; gap: 8px; } @@ -168,14 +166,6 @@ .stat span { display: block; font-size: 11px; color: var(--muted); } .stat strong { font-size: 18px; } -.meter-row { - display: grid; - grid-template-columns: 72px 1fr; - align-items: center; - gap: 8px; - font-size: 12px; - margin: 6px 0; -} meter { width: 100%; height: 12px; @@ -340,16 +330,6 @@ meter { -.tree-note { - color: var(--muted); - font-size: 12px; - line-height: 1.45; - margin: 0 0 10px; - padding: 8px 10px; - border-radius: 10px; - background: rgba(255,255,255,0.54); - border: 1px solid rgba(84,68,48,0.10); -} /* Clearer tool category grouping. */ diff --git a/css/mobile.css b/css/mobile.css index f671237..c79cdf6 100644 --- a/css/mobile.css +++ b/css/mobile.css @@ -1,41 +1,3 @@ -.sound-category-controls, -.log-push-controls { - margin-top: 10px; - padding: 10px; - border-radius: 14px; - border: 1px solid rgba(84,68,48,.12); - background: rgba(255,255,255,.46); - font-size: 12px; -} -.control-group-title { font-weight: 700; margin-bottom: 7px; color: rgba(58,48,39,.88); } -.sound-category-controls { display: grid; grid-template-columns: 1fr; gap: 6px; } -.sound-category-controls label, -.push-master, -.push-kind-grid label { display: flex; align-items: center; gap: 6px; cursor: pointer; user-select: none; } -.sound-category-controls input, -.push-master input, -.push-kind-grid input { accent-color: #82bf66; } -.log-push-controls { display: flex; flex-direction: column; gap: 8px; } -.push-kind-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 5px 8px; } -.log-push-overlay { position: fixed; left: 18px; bottom: 18px; z-index: 9998; width: min(320px, calc(100vw - 36px)); display: flex; flex-direction: column; gap: 8px; pointer-events: none; } -.log-push-toast { position: relative; overflow: hidden; padding: 10px 12px 10px 17px; border-radius: 15px; background: rgba(255,252,246,.94); border: 1px solid rgba(84,68,48,.16); box-shadow: 0 12px 32px rgba(44,34,25,.18); animation: logPushIn .18s ease-out; } -.log-push-toast::before { content: ""; position: absolute; inset: 0 auto 0 0; width: 5px; background: #a5c77a; } -.log-push-toast .log-push-tag { display: inline-block; margin-right: 6px; font-size: 11px; font-weight: 700; color: rgba(54,45,38,.88); } -.log-push-toast .log-push-time { font-size: 10px; color: rgba(104,88,72,.72); } -.log-push-toast .log-push-text { margin-top: 3px; font-size: 12px; line-height: 1.35; color: rgba(48,40,33,.92); } -.log-push-toast.log-push-death::before { background: #8f817a; } -.log-push-toast.log-push-accident::before { background: #ee9e5d; } -.log-push-toast.log-push-birth::before { background: #98cf7a; } -.log-push-toast.log-push-fight::before { background: #e47b80; } -.log-push-toast.log-push-relation::before { background: #b99be0; } -.log-push-toast.log-push-food::before { background: #e9bd61; } -.log-push-toast.log-push-weather::before { background: #7bbbdc; } -.log-push-toast.log-push-grass::before { background: #8cc77a; } -.log-push-toast.log-push-event::before { background: #8b82d6; } -.log-push-toast.leaving { opacity: 0; transform: translateY(8px); transition: opacity .22s ease, transform .22s ease; } -@keyframes logPushIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } - - /* v15.12.1: top-right sound settings panel */ .sound-menu { position: relative; display: inline-block; } .sound-menu-btn.active { background: linear-gradient(180deg, #f3ffe9, #e7f8db); border-color: rgba(103,178,44,.46); } @@ -71,9 +33,6 @@ } /* v15.12.4: sound settings live only in the top-right sound menu */ -.log-card .sound-category-controls { - display: none !important; -} /* v15.17.0: mobile layout isolation and canvas touch gestures */ #app { overflow-x: clip; } diff --git a/css/panel.css b/css/panel.css index 222c8d4..9e0fa03 100644 --- a/css/panel.css +++ b/css/panel.css @@ -1,9 +1,4 @@ /* CSS split file; preserve manifest load order. */ -.lineage-tree { - overflow: auto; - padding: 8px 10px 18px; - contain: content; -} .lineage-main-card { margin-top: 16px; @@ -52,11 +47,6 @@ gap: 12px; } -.lineage-main-content .lineage-tree { - min-width: 360px; - overflow: visible; - padding-right: 2px; -} body::before { @@ -192,14 +182,6 @@ body::before { text-overflow: ellipsis; white-space: nowrap; } -.family-copy .death-reason { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - line-height: 1.12; - margin-top: 1px; - color: rgba(126, 80, 64, 0.92); -} .family-personality-tags { display: grid !important; grid-template-columns: repeat(2, minmax(0, 1fr)); @@ -314,20 +296,6 @@ body::before { background: rgba(255,253,248,0.82); border-radius: 8px; } -.lineage-tree-hint { - position: sticky; - left: 0; - z-index: 3; - width: max-content; - max-width: 100%; - margin: -2px 0 8px; - padding: 5px 8px; - border-radius: 9px; - color: var(--muted); - font-size: 11px; - background: rgba(255,255,255,0.62); - border: 1px solid rgba(84,68,48,0.08); -} .lineage-family-tree .lineage-tree-panel { position: relative; overflow: visible; diff --git a/index.html b/index.html index 74626b8..045871b 100644 --- a/index.html +++ b/index.html @@ -7,11 +7,11 @@ - - - - - + + + + +
@@ -201,158 +201,152 @@
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/js/ants.js b/js/ants.js index 3f3089f..fc69f0a 100644 --- a/js/ants.js +++ b/js/ants.js @@ -13,14 +13,6 @@ const ANT_WORKER_RETURN_WEAK_SPEED = 9.0; const ANT_WORKER_DRAG_SPEED = 6.1875; const ANT_QUEEN_SPEED = 13.5; -const ANT_EXTENSION_HOOKS = Object.freeze({ - nestHunger: "reserved", - nestGrowth: "reserved", - queenReproduction: "existing-queen-founding", - workerLifecycle: "worker-pool-hp", - carryingSuccessRate: "current-deterministic-haul", - territorialBehavior: "reserved", -}); function clampAntHp(v) { return clamp(Number(v) || 0, 0, ANT_WORKER_HP); @@ -84,7 +76,6 @@ class AntActor { this.seed = opts.seed || Math.random() * 1000; this.wanderAngle = opts.wanderAngle || rand(0, Math.PI * 2); this.nextTurn = opts.nextTurn || rand(0.4, 1.6); - this.returnTimer = opts.returnTimer || 0; this.foundingX = opts.foundingX; this.foundingY = opts.foundingY; this.foundingDelayUntil = Number.isFinite(opts.foundingDelayUntil) ? opts.foundingDelayUntil : -Infinity; @@ -292,7 +283,6 @@ class AntActor { target.target = null; if (targetWasSleeping) { target.startSleeping?.(null, "\u7720\u3063\u305f\u307e\u307e\u30a2\u30ea\u306b\u904b\u3070\u308c\u3066\u3044\u308b"); - target.sleepTimer = Math.max(target.sleepTimer || 0, 0.8); } else { if (target.enterPanic) target.enterPanic({ threat: this, reason: "\u30a2\u30ea\u306b\u5de3\u3078\u904b\u3070\u308c\u3066\u3044\u308b", fear: 0.36, wake: true, cause: "ant_drag" }); else { target.setActionState?.("panic", { target: target.panicDestination ? target.panicDestination(this) : null, reason: "\u30a2\u30ea\u306b\u5de3\u3078\u904b\u3070\u308c\u3066\u3044\u308b", wake: true }); target.fearTimer = Math.max(target.fearTimer || 0, 0.36); } @@ -428,7 +418,7 @@ class AntActor { draw(ctx, time = 0, lighting = null) { if (this.dead) return; const id = this.spriteId(); - const img = typeof getRenderableImage === "function" ? getRenderableImage(id, id) : images.get(id); + const img = getRenderableImage(id, id); const lightState = lighting || getLightingState(this.world); ctx.save(); const shadow = projectedShadowParams(lightState, this.kind === "queen" ? 0.78 : 0.26); @@ -479,13 +469,3 @@ class AntActor { } } -window.AntActor = AntActor; -window.ANT_NEST_START_COUNT = ANT_NEST_START_COUNT; -window.ANT_NEST_MAX_COUNT = ANT_NEST_MAX_COUNT; -window.ANT_NEST_MAX_OUTSIDE = ANT_NEST_MAX_OUTSIDE; -window.ANT_WORKER_HP = ANT_WORKER_HP; -window.ANT_QUEEN_HP = ANT_QUEEN_HP; -window.ANT_EXTENSION_HOOKS = ANT_EXTENSION_HOOKS; -window.buildAntWorkerPool = buildAntWorkerPool; -window.ensureAntNestWorkerPool = ensureAntNestWorkerPool; -window.syncAntNestCount = syncAntNestCount; diff --git a/js/assets.js b/js/assets.js index dd44218..7142902 100644 --- a/js/assets.js +++ b/js/assets.js @@ -1,16 +1,70 @@ "use strict"; const images = new Map(); -const imageBoundsCache = new WeakMap(); const imageMetrics = new Map(); const imagePromises = new Map(); const imageAssetIndex = new Map(); +const DECOR_ASSETS = [ + { id: "zunchi", path: "assets/objects/zunchi.webp" }, + { id: "zunchi_02", path: "assets/objects/zunchi_02.webp" }, + { id: "ant_queen", path: "assets/objects/ant_queen.webp" }, + { id: "ant_worker", path: "assets/objects/ant_worker.webp" }, + { id: "genkotsu", path: "assets/objects/genkotsu.webp" }, + { id: "pushpin", path: "assets/objects/pushpin.webp" }, + { id: "pushpin_stuck", path: "assets/objects/pushpin_stuck.webp" }, + { id: "oshibyo", path: "assets/objects/oshibyo.webp" }, + { id: "oshibyo_stuck", path: "assets/objects/oshibyo_stuck.webp" }, + { id: "plushie_bear", path: "assets/objects/plushie_bear.png" }, +]; + +const ALPHA_BOUNDS = { + smile: { x: 18, y: 18, w: 476, h: 432, imageW: 512, imageH: 468 }, + angry: { x: 16, y: 16, w: 480, h: 432, imageW: 512, imageH: 464 }, + teary: { x: 17, y: 17, w: 478, h: 438, imageW: 512, imageH: 472 }, + jito: { x: 18, y: 18, w: 476, h: 451, imageW: 512, imageH: 487 }, + drool: { x: 18, y: 18, w: 476, h: 425, imageW: 512, imageH: 461 }, + cry: { x: 15, y: 15, w: 482, h: 448, imageW: 512, imageH: 478 }, + sleep: { x: 18, y: 18, w: 475, h: 439, imageW: 511, imageH: 475 }, + pokan: { x: 18, y: 18, w: 476, h: 433, imageW: 512, imageH: 469 }, + stretch: { x: 15, y: 15, w: 482, h: 354, imageW: 512, imageH: 384 }, + hurt: { x: 17, y: 21, w: 478, h: 432, imageW: 512, imageH: 468 }, + weak: { x: 53, y: 18, w: 405, h: 435, imageW: 512, imageH: 468 }, + back: { x: 17, y: 21, w: 478, h: 432, imageW: 512, imageH: 468 }, + flee: { x: 17, y: 21, w: 478, h: 432, imageW: 512, imageH: 468 }, + zunda_eat: { x: 17, y: 21, w: 478, h: 432, imageW: 512, imageH: 468 }, + hurt2: { x: 16, y: 20, w: 480, h: 434, imageW: 512, imageH: 468 }, + normal_smirk: { x: 16, y: 20, w: 480, h: 434, imageW: 512, imageH: 468 }, + normal_tongue: { x: 16, y: 20, w: 480, h: 434, imageW: 512, imageH: 468 }, + fear: { x: 16, y: 20, w: 480, h: 434, imageW: 512, imageH: 468 }, + flee_fear2: { x: 16, y: 26, w: 480, h: 428, imageW: 512, imageH: 468 }, + sleep2: { x: 16, y: 21, w: 480, h: 433, imageW: 512, imageH: 468 }, + normal_happy: { x: 16, y: 21, w: 480, h: 433, imageW: 512, imageH: 468 }, + fear_blue: { x: 16, y: 21, w: 480, h: 433, imageW: 512, imageH: 468 }, + fear_cry: { x: 16, y: 21, w: 480, h: 433, imageW: 512, imageH: 468 }, + stress_dizzy: { x: 16, y: 21, w: 480, h: 433, imageW: 512, imageH: 468 }, + stress_sweat: { x: 16, y: 26, w: 480, h: 428, imageW: 512, imageH: 468 }, + intimidate: { x: 98, y: 16, w: 316, h: 438, imageW: 512, imageH: 468 }, + zunchi_slave: { x: 17, y: 22, w: 478, h: 431, imageW: 512, imageH: 468 }, + hungry_70: { x: 0, y: 0, w: 430, h: 366, imageW: 430, imageH: 366 }, + zunchi_slave_alt: { x: 42, y: 22, w: 425, h: 426, imageW: 512, imageH: 468 }, + zunchi: { x: 0, y: 0, w: 236, h: 178, imageW: 236, imageH: 178 }, + zunchi_02: { x: 8, y: 63, w: 220, h: 105, imageW: 236, imageH: 178 }, + ant_queen: { x: 0, y: 0, w: 300, h: 155, imageW: 300, imageH: 155 }, + ant_worker: { x: 0, y: 0, w: 140, h: 72, imageW: 140, imageH: 72 }, + genkotsu: { x: 8, y: 8, w: 220, h: 283, imageW: 236, imageH: 299 }, + sunbath_1: { x: 71, y: 18, w: 369, h: 432, imageW: 512, imageH: 468 }, + sunbath_2: { x: 69, y: 18, w: 374, h: 432, imageW: 512, imageH: 468 }, + sunbath_3: { x: 71, y: 18, w: 369, h: 432, imageW: 512, imageH: 468 }, + plushie_bear: { x: 0, y: 0, w: 150, h: 192, imageW: 150, imageH: 192 }, +}; + + const scaledSpriteCache = new Map(); const stainOverlayCache = new Map(); const SPRITE_CACHE_LIMIT = 128; const STAIN_CACHE_LIMIT = 64; -const SUNBATH_IMAGE_IDS = new Set((typeof SPRITES !== "undefined" ? SPRITES : []) +const SUNBATH_IMAGE_IDS = new Set(SPRITES .map(asset => asset?.id || "") .filter(id => String(id).startsWith("sunbath"))); const INITIAL_IMAGE_IDS = new Set(["smile", "hungry_70", "zunchi_slave", "zunchi_slave_alt", "zunchi", "zunchi_02", "genkotsu", "pushpin", "pushpin_stuck", "oshibyo", "oshibyo_stuck", ...SUNBATH_IMAGE_IDS]); @@ -35,13 +89,6 @@ function getCachedSpriteCanvas(id, img, w, h) { const qw = quantizedCanvasSize(w); const qh = quantizedCanvasSize(h); const key = `${id}:${qw}x${qh}`; - if (window.TarinaiPhysics?.createCachedCanvas) { - return window.TarinaiPhysics.createCachedCanvas(scaledSpriteCache, key, qw, qh, (c) => { - if ("imageSmoothingQuality" in c) c.imageSmoothingQuality = "high"; - c.clearRect(0, 0, qw, qh); - c.drawImage(img, 0, 0, qw, qh); - }, { limit: SPRITE_CACHE_LIMIT }); - } let canvas = scaledSpriteCache.get(key); if (canvas) return canvas; canvas = document.createElement("canvas"); @@ -86,7 +133,6 @@ function getCachedStainOverlayCanvas(id, img, w, h, stain = 0, seed = 0.5) { c.globalAlpha = 1; c.globalCompositeOperation = "source-over"; }; - if (window.TarinaiPhysics?.createCachedCanvas) return window.TarinaiPhysics.createCachedCanvas(stainOverlayCache, key, qw, qh, buildOverlay, { limit: STAIN_CACHE_LIMIT }); let canvas = stainOverlayCache.get(key); if (canvas) return canvas; canvas = document.createElement("canvas"); @@ -109,7 +155,6 @@ function primeImageMetrics(asset) { w, h, ratio: h / Math.max(w, 1), - visibleBottomRatio: bounds ? (bounds.y + bounds.h) / Math.max(bounds.imageH, 1) : 1, }); } @@ -129,7 +174,6 @@ function markImageLoaded(asset, img) { w, h, ratio: h / Math.max(w, 1), - visibleBottomRatio: (bounds.y + bounds.h) / Math.max(bounds.imageH, 1), }); } @@ -154,7 +198,7 @@ function loadImageAsset(asset, priority = "auto") { img.onerror = () => { resolve(null); }; }); imagePromises.set(asset.id, promise); - if (!img.src) img.src = (window.TarinaiVersion?.withStaticVersion ? window.TarinaiVersion.withStaticVersion(asset.path) : asset.path); + if (!img.src) img.src = window.TARINAI_APP?.withVersion ? window.TARINAI_APP.withVersion(asset.path) : asset.path; return promise; } @@ -176,10 +220,9 @@ function getRenderableImage(id, fallbackId = "smile") { return null; } -function loadImages(ids = null, onProgress = null) { +function loadImages(onProgress = null) { setupAssetIndex(); - const requested = ids ? new Set(ids) : INITIAL_IMAGE_IDS; - const assets = [...requested].map(id => imageAssetIndex.get(id)).filter(Boolean); + const assets = [...INITIAL_IMAGE_IDS].map(id => imageAssetIndex.get(id)).filter(Boolean); const total = Math.max(assets.length, 1); let done = 0; if (typeof onProgress === "function") onProgress(done, total, null); @@ -227,14 +270,9 @@ function startBackgroundImageLoading() { function imageAlphaBounds(img) { if (!img) return null; if (img.alphaBounds) return img.alphaBounds; - if (imageBoundsCache.has(img)) return imageBoundsCache.get(img); - const w = img.naturalWidth || img.width || 0; const h = img.naturalHeight || img.height || 0; - if (!w || !h) return null; - const bounds = { x: 0, y: 0, w, h, imageW: w, imageH: h }; - imageBoundsCache.set(img, bounds); - return bounds; + return w && h ? { x: 0, y: 0, w, h, imageW: w, imageH: h } : null; } function getImageMetrics(idOrImg) { @@ -250,9 +288,8 @@ function getImageMetrics(idOrImg) { } const w = idOrImg.naturalWidth || idOrImg.width || 0; const h = idOrImg.naturalHeight || idOrImg.height || 0; - return w && h ? { w, h, ratio: h / w, visibleBottomRatio: 1 } : null; + return w && h ? { w, h, ratio: h / w } : null; } setupAssetIndex(); -window.TarinaiAssets = { images, imageBoundsCache, imageMetrics, imagePromises, scaledSpriteCache, stainOverlayCache, loadImages, startBackgroundImageLoading, trimImageMemory, ensureImage, isImageReady, getRenderableImage, imageAlphaBounds, getImageMetrics, getCachedSpriteCanvas, getCachedStainOverlayCanvas }; diff --git a/js/audio.js b/js/audio.js index 0fd084b..aeb24b3 100644 --- a/js/audio.js +++ b/js/audio.js @@ -10,12 +10,8 @@ const audio = { sampleLast: {}, activeVoices: 0, maxConcurrent: 96, - failedSamples: {}, lastPlayedId: "", - lastPlayedAt: 0, - soundPackVersion: "inline", masterGainNode: null, - compressorNode: null, _synthIdGain: 1, idGain: {}, idMinGap: {}, @@ -26,7 +22,6 @@ const audio = { soundMap: {}, applySoundPack(pack = window.TARINAI_SOUND_PACK) { if (!pack || typeof pack !== "object") return false; - this.soundPackVersion = String(pack.version || "custom"); if (pack.categoryGain && typeof pack.categoryGain === "object") this.categoryGain = { ...this.categoryGain, ...pack.categoryGain }; if (pack.idGain && typeof pack.idGain === "object") this.idGain = { ...this.idGain, ...pack.idGain }; if (pack.idMinGap && typeof pack.idMinGap === "object") this.idMinGap = { ...this.idMinGap, ...pack.idMinGap }; @@ -46,13 +41,13 @@ const audio = { this.masterGainNode = ctx.createGain(); this.masterGainNode.gain.setValueAtTime(1.85, ctx.currentTime); if (typeof ctx.createDynamicsCompressor === "function") { - this.compressorNode = ctx.createDynamicsCompressor(); - this.compressorNode.threshold.value = -18; - this.compressorNode.knee.value = 24; - this.compressorNode.ratio.value = 8; - this.compressorNode.attack.value = 0.002; - this.compressorNode.release.value = 0.18; - this.masterGainNode.connect(this.compressorNode).connect(ctx.destination); + const compressor = ctx.createDynamicsCompressor(); + compressor.threshold.value = -18; + compressor.knee.value = 24; + compressor.ratio.value = 8; + compressor.attack.value = 0.002; + compressor.release.value = 0.18; + this.masterGainNode.connect(compressor).connect(ctx.destination); } else { this.masterGainNode.connect(ctx.destination); } @@ -95,8 +90,7 @@ const audio = { a.preload = "auto"; a.volume = this.sampleVolumeFor(key); a.addEventListener("error", () => { - this.failedSamples[key] = path; - window.TarinaiEvents?.emit("audio:sample-error", { id: key, path }); + window.TarinaiEvents.emit("audio:sample-error", { id: key, path }); }, { once: true }); this.samples[key] = a; } @@ -147,8 +141,7 @@ const audio = { const cat = category || this.categoryFor(key, key.startsWith("voice") ? "voice" : "ops"); if (!this.canPlay(key, cat, minGap)) return false; this.lastPlayedId = key; - this.lastPlayedAt = performance.now() / 1000; - window.TarinaiEvents?.emit("audio:play", { id: key, category: cat, synthetic: false }); + window.TarinaiEvents.emit("audio:play", { id: key, category: cat, synthetic: false }); this.ensure(); const node = base.cloneNode(true); node.volume = Math.min(1, base.volume * (this.categoryGain?.[cat] ?? 1) * (this.idGain?.[key] ?? 1)); @@ -203,8 +196,7 @@ const audio = { const minGap = opts.minGap ?? 0.10; if (!this.canPlay(id, category, minGap)) return false; this.lastPlayedId = id; - this.lastPlayedAt = performance.now() / 1000; - window.TarinaiEvents?.emit("audio:play", { id, category, synthetic: !this.samples[id] }); + window.TarinaiEvents.emit("audio:play", { id, category, synthetic: !this.samples[id] }); if (this.samples[id]) { const base = this.samples[id]; this.ensure(); @@ -227,7 +219,7 @@ const audio = { }, ms); try { switch (id) { - case "sfx_notify": case "sfx_log_push": + case "sfx_notify": this.tone(880, 0.055, "sine", 0.022, 80, category); t(62, () => this.tone(1180, 0.075, "triangle", 0.020, -40, category)); break; case "sfx_ui_click": this.tone(520, 0.035, "square", 0.010, 80, category); break; case "sfx_ui_fold": this.tone(420, 0.045, "triangle", 0.012, -130, category); break; @@ -237,7 +229,6 @@ const audio = { case "sfx_grab": this.tone(300, 0.050, "triangle", 0.018, 90, category); break; case "sfx_drop": this.tone(220, 0.060, "triangle", 0.018, -80, category); break; case "sfx_water_hose": this.noise(0.13, 0.018, category, 2400); break; - case "sfx_water_clean": this.noise(0.08, 0.014, category, 3200); this.tone(1120, 0.040, "sine", 0.009, 180, category); break; case "sfx_drink": this.tone(720, 0.045, "sine", 0.012, -120, category); t(42, () => this.tone(620, 0.040, "sine", 0.010, -80, category)); break; case "sfx_ball_hit": this.tone(260, 0.065, "triangle", 0.020, 120, category); break; case "sfx_stone_impact": this.tone(110, 0.080, "sawtooth", 0.032, -50, category); this.noise(0.05, 0.018, category, 600); break; @@ -319,5 +310,4 @@ const audio = { audio.applySoundPack?.(window.TARINAI_SOUND_PACK); audio.loadSettings(); -window.TarinaiEvents?.on?.("audio:test", ev => audio.play(ev.detail?.id || "sfx_notify", ev.detail || {})); window.TarinaiAudio = audio; diff --git a/js/collision_footprint_system.js b/js/collision_footprint_system.js index 1288e5e..b27f2bf 100644 --- a/js/collision_footprint_system.js +++ b/js/collision_footprint_system.js @@ -169,7 +169,7 @@ function placementRectsFor(worldRef, item) { const solid = worldRef?.solidObstacleRects?.(item) || []; if (solid.length) return solid; - if (global.TarinaiMechanicalSystem?.isMechanicalType?.(item?.type)) return global.TarinaiMechanicalSystem?.placementRects?.(item) || []; + if (global.TarinaiMechanicalSystem.isMechanicalType(item?.type)) return global.TarinaiMechanicalSystem.placementRects(item) || []; return solid; } @@ -177,7 +177,7 @@ if (!item || item.dead) return { hit: false, distance: Infinity }; const padding = Math.max(0, num(opts.padding, 0)); let rects = opts.rects || worldRef?.solidObstacleRects?.(item) || []; - if ((!rects || !rects.length) && item?.type === "poison_block") rects = global.TarinaiMechanicalSystem?.poisonHazardRects?.(item) || []; + if ((!rects || !rects.length) && item?.type === "poison_block") rects = global.TarinaiMechanicalSystem.poisonHazardRects(item) || []; let bestDistance = Math.hypot(num(x) - num(item.x), num(y) - num(item.y)); let hit = false; if (rects.length) { @@ -186,9 +186,9 @@ bestDistance = Math.min(bestDistance, Math.hypot(num(x) - num(rect.cx, item.x), num(y) - num(rect.cy, item.y))); } } - const mechHit = global.TarinaiMechanicalSystem?.isMechanicalType?.(item.type); + const mechHit = global.TarinaiMechanicalSystem.isMechanicalType(item.type); if (mechHit) { - const hub = Math.max(18, Math.min(40, (global.TarinaiPhysicsBodySystem?.scalar?.(item, "thickness", 12) ?? 12) * 2.3)); + const hub = Math.max(18, Math.min(40, (global.TarinaiPhysicsBodySystem.scalar(item, "thickness", 12) ?? 12) * 2.3)); if (Math.hypot(num(x) - num(item.x), num(y) - num(item.y)) <= hub + padding) hit = true; } else if (!rects.length) { const mul = num(opts.radiusMultiplier, item.type === "ball" ? 4.0 : 2.2); @@ -201,7 +201,7 @@ function itemPlacementOverlapBlocked(worldRef, item, opts = {}) { if (!worldRef || !item || item.dead) return true; const important = opts.important || new Set(["zunchi", "grass", "grass_bed", "water", "fence_v", "fence_h", "bounce_fence", "bounce_fence_v", "gate_fence", "rotator", "poison_block", "reciprocator", "nest_box"]); - const isPhysicsItem = (global.TarinaiMechanicalSystem?.isMechanicalType?.(item.type) || item.type === "rope" || item.type === "rod" || (typeof isFenceItemType === "function" && isFenceItemType(item.type))); + const isPhysicsItem = (global.TarinaiMechanicalSystem.isMechanicalType(item.type) || item.type === "rope" || item.type === "rod" || isFenceItemType(item.type)); if (!important.has(item.type) && !isPhysicsItem) return false; const itemRects = placementRectsFor(worldRef, item); const itemRadius = itemFootprintRadius(worldRef, item, itemRects); @@ -212,7 +212,7 @@ if (!important.has(other.type)) continue; const otherRects = placementRectsFor(worldRef, other); const otherRadius = itemFootprintRadius(worldRef, other, otherRects); - const hasMechanism = global.TarinaiMechanicalSystem?.isMechanicalType?.(item.type) || global.TarinaiMechanicalSystem?.isMechanicalType?.(other.type); + const hasMechanism = global.TarinaiMechanicalSystem.isMechanicalType(item.type) || global.TarinaiMechanicalSystem.isMechanicalType(other.type); const itemIsFence = typeof global.isFenceItemType === "function" ? global.isFenceItemType(item.type) : /fence/.test(String(item.type || "")); const otherIsFence = typeof global.isFenceItemType === "function" ? global.isFenceItemType(other.type) : /fence/.test(String(other.type || "")); const margin = (itemIsFence || otherIsFence) ? (item.type === "gate_fence" || other.type === "gate_fence" ? 1.5 : 2.5) : (hasMechanism ? 1.25 : Math.max(4, Math.min(itemRadius, otherRadius) * 0.18)); @@ -237,15 +237,9 @@ rectLocalPoint, rectWorldVector, pointInRect, - rectCorners, - rectAxes, - projectPoints, - aabbOverlap, - rectOverlapInfo, rectsOverlap, rectCircleOverlap, itemFootprintRadius, - isSoftPlacementIgnoredType, placementRectsFor, hitTestItem, itemPlacementOverlapBlocked, diff --git a/js/collision_response_system.js b/js/collision_response_system.js index d636d74..d8db40b 100644 --- a/js/collision_response_system.js +++ b/js/collision_response_system.js @@ -1,42 +1,18 @@ "use strict"; (function (global) { - const ImpactCore = global.TarinaiImpactCoreSystem || {}; + const ImpactCore = global.TarinaiImpactCoreSystem; const PHYSICAL_DAMAGE_SPEED_THRESHOLD = ImpactCore.PHYSICAL_DAMAGE_SPEED_THRESHOLD || 165; const physicalDamageFromImpactSpeed = ImpactCore.physicalDamageFromImpactSpeed; const itemHasAttachedLink = ImpactCore.itemHasAttachedLink; - function pairKey(a, b) { - const aid = String(a?.id ?? "a"); - const bid = String(b?.id ?? "b"); - return aid < bid ? `${aid}:${bid}` : `${bid}:${aid}`; - } - - function normalizeVector(x, y, fallback = null) { - const d = Math.hypot(Number(x) || 0, Number(y) || 0); - if (d > 0.0001) return { x: x / d, y: y / d, d }; - if (fallback) return { x: fallback.x || 1, y: fallback.y || 0, d: 1 }; - return { x: 1, y: 0, d: 1 }; - } - - function separatePair(worldRef, a, b, nx, ny, overlap, aWeight = 0.5, bWeight = 0.5) { - const amount = Math.max(0, Number(overlap || 0) || 0); - if (!worldRef || amount <= 0 || !a || !b) return false; - a.x = clamp((a.x || 0) - nx * amount * aWeight, CONFIG.worldPadding, worldRef.w - CONFIG.worldPadding); - a.y = clamp((a.y || 0) - ny * amount * aWeight, CONFIG.worldPadding, worldRef.h - CONFIG.worldPadding); - b.x = clamp((b.x || 0) + nx * amount * bWeight, CONFIG.worldPadding, worldRef.w - CONFIG.worldPadding); - b.y = clamp((b.y || 0) + ny * amount * bWeight, CONFIG.worldPadding, worldRef.h - CONFIG.worldPadding); - return true; - } - function resolveBallBallCollisions(worldRef, dt) { if (!worldRef) return undefined; - return (function resolveBallBallCollisionsUnified(dt) { - if ((this.itemCounts?.ball || 0) < 2) return; - const now = this.time || 0; - if (!this.ballCollisionMemo) this.ballCollisionMemo = new Map(); - const balls = this.itemsOfType?.("ball") || (this.items || []).filter(it => it && !it.dead && it.type === "ball"); + if ((worldRef.itemCounts?.ball || 0) < 2) return; + const now = worldRef.time || 0; + if (!worldRef.ballCollisionMemo) worldRef.ballCollisionMemo = new Map(); + const balls = worldRef.itemsOfType?.("ball") || (worldRef.items || []).filter(it => it && !it.dead && it.type === "ball"); for (const a of balls) { if (!a || a.dead || a.type !== "ball") continue; const ar = a.r || 18; @@ -44,10 +20,10 @@ const avy = a.vy || 0; const aSpeed = Math.hypot(avx, avy); const range = ar * 2 + aSpeed * Math.max(0.016, dt || 0.016) + 52; - for (const b of this.nearbyItems(a.x, a.y, range) || []) { + for (const b of worldRef.nearbyItems(a.x, a.y, range) || []) { if (!b || b === a || b.dead || b.type !== "ball") continue; const key = a.id < b.id ? `${a.id}:${b.id}` : `${b.id}:${a.id}`; - if (now - (this.ballCollisionMemo.get(key) || -999) < 0.055) continue; + if (now - (worldRef.ballCollisionMemo.get(key) || -999) < 0.055) continue; const br = b.r || 18; const hitRadius = ar + br; let dx = b.x - a.x; @@ -90,7 +66,7 @@ const rvy = (b.vy || 0) - (a.vy || 0); const rs = Math.hypot(rvx, rvy); if (rs > 0.001) { dx = rvx / rs; dy = rvy / rs; d = 1; } - else { const ang = deterministicAngle(this, "ball-ball-overlap-normal", a, b); dx = Math.cos(ang); dy = Math.sin(ang); d = 1; } + else { const ang = deterministicAngle(worldRef, "ball-ball-overlap-normal", a, b); dx = Math.cos(ang); dy = Math.sin(ang); d = 1; } } const nx = dx / d; const ny = dy / d; @@ -98,7 +74,7 @@ const rvy = (b.vy || 0) - (a.vy || 0); const relNormal = rvx * nx + rvy * ny; if (relNormal > 0 && d >= hitRadius - 0.5) continue; - this.ballCollisionMemo.set(key, now); + worldRef.ballCollisionMemo.set(key, now); const restitution = 0.86; const impulse = -(1 + restitution) * relNormal / 2; if (Number.isFinite(impulse)) { @@ -117,19 +93,17 @@ const tangent = nx * ((a.vy || 0) - (b.vy || 0)) - ny * ((a.vx || 0) - (b.vx || 0)); a.spinVelocity = clamp((a.spinVelocity || 0) - tangent / Math.max(14, ar) * 1.8, -38, 38); b.spinVelocity = clamp((b.spinVelocity || 0) + tangent / Math.max(14, br) * 1.8, -38, 38); - this.effects.push(new Effect("ring", hitX, hitY, { size: Math.max(13, hitRadius * 0.38), life: 0.16, color: "rgba(255,255,255,0.44)" })); + worldRef.effects.push(new Effect("ring", hitX, hitY, { size: Math.max(13, hitRadius * 0.38), life: 0.16, color: "rgba(255,255,255,0.44)" })); } } - }).call(worldRef, dt); } function resolveBallInteractions(worldRef, dt) { if (!worldRef) return undefined; - return (function resolveBallInteractionsUnified(dt) { - if (!this.itemCounts?.ball) return; - for (const ball of (this.itemsOfType ? this.itemsOfType("ball") : this.items)) { + if (!worldRef.itemCounts?.ball) return; + for (const ball of (worldRef.itemsOfType ? worldRef.itemsOfType("ball") : worldRef.items)) { if (!ball || ball.dead || ball.type !== "ball") continue; const ballSpeed = Math.hypot(ball.vx || 0, ball.vy || 0); const sweep = ballSpeed * Math.max(0.016, dt || 0.016) + 84; @@ -139,8 +113,8 @@ const sx = ball.x - px; const sy = ball.y - py; const segLenSq = sx * sx + sy * sy; - for (const t of this.nearbyTarinai(ball.x, ball.y, range)) { - if (!t || t.dead || t.state === "sleep" || this.isTarinaiHiddenInNestBox(t)) continue; + for (const t of worldRef.nearbyTarinai(ball.x, ball.y, range)) { + if (!t || t.dead || t.state === "sleep" || worldRef.isTarinaiHiddenInNestBox(t)) continue; let hitX = ball.x; let hitY = ball.y; if (segLenSq > 1) { @@ -151,7 +125,7 @@ let d = Math.max(0.001, distXY(hitX, hitY, t.x, t.y)); const hitDistance = (ball.r || 18) + t.radius * 0.74; if (d > hitDistance) continue; - const now = this.time || 0; + const now = worldRef.time || 0; const repeat = ball.lastKickerId === t.id && now - (ball.lastKickedAt || -999) < 0.18; if (repeat && ballSpeed < 300) continue; let nx = (hitX - t.x) / d; @@ -162,25 +136,25 @@ ny = (ball.vy || 0) / ballSpeed; } else { nx = t.facingDir ? t.facingDir() : 1; - ny = deterministicRange(this, "ball-interaction-fallback-ny", -0.22, 0.22, ball, t); + ny = deterministicRange(worldRef, "ball-interaction-fallback-ny", -0.22, 0.22, ball, t); } } - const transferredPins = globalThis.TarinaiItemDynamicPinSystem?.transferBallPinsToTarinai?.(ball, t, this) || 0; + const transferredPins = globalThis.TarinaiItemDynamicPinSystem.transferBallPinsToTarinai(ball, t, worldRef) || 0; if (transferredPins > 0) { ball.vx = (ball.vx || 0) * 0.72 - nx * Math.min(78, Math.max(18, ballSpeed * 0.06)); ball.vy = (ball.vy || 0) * 0.72 - ny * Math.min(78, Math.max(18, ballSpeed * 0.06)); ball.spinVelocity = clamp((ball.spinVelocity || 0) * 0.72 + (nx >= 0 ? -1 : 1) * 5.2, -38, 38); - ball.x = clamp(t.x + nx * (hitDistance + 2), CONFIG.worldPadding, this.w - CONFIG.worldPadding); - ball.y = clamp(t.y + ny * (hitDistance + 2), CONFIG.worldPadding, this.h - CONFIG.worldPadding); + ball.x = clamp(t.x + nx * (hitDistance + 2), CONFIG.worldPadding, worldRef.w - CONFIG.worldPadding); + ball.y = clamp(t.y + ny * (hitDistance + 2), CONFIG.worldPadding, worldRef.h - CONFIG.worldPadding); ball.lastKickedAt = now; ball.lastKickerId = t.id; - this.effects.push(new Effect("ring", hitX, hitY, { size: Math.max(18, ball.r * 1.05), life: 0.20, color: "rgba(196,65,72,0.46)" })); - if (this.relationNotice(t.id, ball.id || "ball", "ball-pin-transfer", 1.2)) this.log(`${t.name}にボールから鋲が移った。`, "accident", { participants: [t] }); + worldRef.effects.push(new Effect("ring", hitX, hitY, { size: Math.max(18, ball.r * 1.05), life: 0.20, color: "rgba(196,65,72,0.46)" })); + if (worldRef.relationNotice(t.id, ball.id || "ball", "ball-pin-transfer", 1.2)) worldRef.log(`${t.name}にボールから鋲が移った。`, "accident", { participants: [t] }); continue; } - const constrainedBall = itemHasAttachedLink(this, ball); + const constrainedBall = itemHasAttachedLink(worldRef, ball); const frameDt = Math.max(0.016, Number(dt || 0.016) || 0.016); const moveVx = sx / frameDt; const moveVy = sy / frameDt; @@ -195,10 +169,10 @@ t.vy = clamp((t.vy || 0) - ny * pushImpulse + effectiveVy * 0.08, -360, 360); if (overlapPush > 0.4) { const sep = Math.min(14, overlapPush * 0.58 + 0.8); - t.x = clamp((t.x || 0) - nx * sep, CONFIG.worldPadding, this.w - CONFIG.worldPadding); - t.y = clamp((t.y || 0) - ny * sep, CONFIG.worldPadding, this.h - CONFIG.worldPadding); - ball.x = clamp((ball.x || 0) + nx * sep * 0.28, CONFIG.worldPadding, this.w - CONFIG.worldPadding); - ball.y = clamp((ball.y || 0) + ny * sep * 0.28, CONFIG.worldPadding, this.h - CONFIG.worldPadding); + t.x = clamp((t.x || 0) - nx * sep, CONFIG.worldPadding, worldRef.w - CONFIG.worldPadding); + t.y = clamp((t.y || 0) - ny * sep, CONFIG.worldPadding, worldRef.h - CONFIG.worldPadding); + ball.x = clamp((ball.x || 0) + nx * sep * 0.28, CONFIG.worldPadding, worldRef.w - CONFIG.worldPadding); + ball.y = clamp((ball.y || 0) + ny * sep * 0.28, CONFIG.worldPadding, worldRef.h - CONFIG.worldPadding); } ball.vx = clamp((ball.vx || effectiveVx) * 0.62 + nx * Math.min(42, pushImpulse * 0.22), -720, 720); ball.vy = clamp((ball.vy || effectiveVy) * 0.62 + ny * Math.min(42, pushImpulse * 0.22), -720, 720); @@ -207,7 +181,7 @@ ball.lastKickerId = t.id; t.target = null; t.surpriseTimer = Math.max(t.surpriseTimer || 0, 0.16); - this.markSpatialDirty?.("constrained-ball-push"); + worldRef.markSpatialDirty?.("constrained-ball-push"); continue; } @@ -217,13 +191,13 @@ const bvy = ballSpeed > 0.01 ? (ball.vy || 0) / ballSpeed : ny; const damage = physicalDamageFromImpactSpeed(ballSpeed, { min: 3.5, max: 38, scale: 18 }); t.lastBallDamageAt = now; - this.applyImpulse(t, (ball.vx || 0) * 0.20, (ball.vy || 0) * 0.20, { + worldRef.applyImpulse(t, (ball.vx || 0) * 0.20, (ball.vy || 0) * 0.20, { panic: true, target: { x: ball.x, y: ball.y }, fearTimer: 0.95, thought: "\u9ad8\u901f\u306e\u30dc\u30fc\u30eb\u306b\u3076\u3064\u304b\u3063\u3066\u30d1\u30cb\u30c3\u30af\u306b\u306a\u3063\u3066\u3044\u308b", }); - this.applyImpactDamage(t, damage, "\u885d\u7a81", { source: ball }); + worldRef.applyImpactDamage(t, damage, "\u885d\u7a81", { source: ball }); const sensitiveHit = t.shouldApplyPersonalityBehavior?.("neuroticism", 1) ? 1.35 : (t.shouldApplyPersonalityBehavior?.("neuroticism", -1) ? 0.82 : 1.0); const stressGain = clamp(damage * 0.55 * sensitiveHit, 3, sensitiveHit > 1 ? 24 : 18); if (t.addStress) t.addStress(stressGain, { threshold: 8 }); @@ -236,9 +210,9 @@ t.fearTimer = Math.max(t.fearTimer, 0.95); t.blastSpinTimer = Math.max(t.blastSpinTimer || 0, damage > 14 ? 1.0 : 0.44); t.blastSpinMax = Math.max(t.blastSpinMax || 0, t.blastSpinTimer); - this.spawnFallEffect(t.x, t.y + t.radius * 0.55, clamp(damage / 18, 0.55, 1.8)); - this.effects.push(new Effect("ring", hitX, hitY, { size: Math.max(20, ball.r * 1.25), life: 0.22, color: "rgba(210,75,65,0.50)" })); - if (this.relationNotice(t.id, ball.id || "ball", "fast-ball-hit", 2.8)) this.log(`${t.name}\u306f\u9ad8\u901f\u306e\u30dc\u30fc\u30eb\u306b\u885d\u7a81\u3057\u3066\u5f3e\u304d\u98db\u3070\u3055\u308c\u305f\u3002`, "accident", { participants: [t] }); + worldRef.spawnFallEffect(t.x, t.y + t.radius * 0.55, clamp(damage / 18, 0.55, 1.8)); + worldRef.effects.push(new Effect("ring", hitX, hitY, { size: Math.max(20, ball.r * 1.25), life: 0.22, color: "rgba(210,75,65,0.50)" })); + if (worldRef.relationNotice(t.id, ball.id || "ball", "fast-ball-hit", 2.8)) worldRef.log(`${t.name}\u306f\u9ad8\u901f\u306e\u30dc\u30fc\u30eb\u306b\u885d\u7a81\u3057\u3066\u5f3e\u304d\u98db\u3070\u3055\u308c\u305f\u3002`, "accident", { participants: [t] }); ball.vx = (ball.vx || 0) * 0.58 - nx * Math.min(110, ballSpeed * 0.10); ball.vy = (ball.vy || 0) * 0.58 - ny * Math.min(110, ballSpeed * 0.10); ball.spinVelocity = clamp((ball.spinVelocity || 0) + (nx >= 0 ? -1 : 1) * 8.5, -38, 38); @@ -255,8 +229,8 @@ ball.vy = (ball.vy || 0) * (isDangerous ? 0.78 : 0.92) + ny * impulse + (t.vy || 0) * 0.52; const kickedSpeed = Math.hypot(ball.vx || 0, ball.vy || 0); ball.spinVelocity = clamp((ball.spinVelocity || 0) + (nx * (t.vy || 0) - ny * (t.vx || 0)) / 16 + impulse / Math.max(12, ball.r || 18) * (nx >= 0 ? 1 : -1), -38, 38); - ball.x = clamp(t.x + nx * (hitDistance + 2), CONFIG.worldPadding, this.w - CONFIG.worldPadding); - ball.y = clamp(t.y + ny * (hitDistance + 2), CONFIG.worldPadding, this.h - CONFIG.worldPadding); + ball.x = clamp(t.x + nx * (hitDistance + 2), CONFIG.worldPadding, worldRef.w - CONFIG.worldPadding); + ball.y = clamp(t.y + ny * (hitDistance + 2), CONFIG.worldPadding, worldRef.h - CONFIG.worldPadding); ball.lastKickedAt = now; ball.lastKickerId = t.id; t.vx -= nx * Math.min(22, impulse * 0.10); @@ -271,26 +245,24 @@ } t.goodMode = playIntent || playful ? "smile" : t.goodMode; if (playIntent && now > (t.nextPlayBubbleAt || 0)) { - t.nextPlayBubbleAt = now + deterministicRange(this, "ball-play-bubble-delay", 2.0, 3.8, ball, t); - this.spawnBubble(t.x, t.y - t.radius * 1.15, pick(["!", "\u306f\u3046", "?" ]), "rgba(70,96,50,0.76)"); + t.nextPlayBubbleAt = now + deterministicRange(worldRef, "ball-play-bubble-delay", 2.0, 3.8, ball, t); + worldRef.spawnBubble(t.x, t.y - t.radius * 1.15, pick(["!", "\u306f\u3046", "?" ]), "rgba(70,96,50,0.76)"); } - if (playful && this.relationNotice(t.id, ball.id || "ball", "ball-kick", 16)) this.log(`${t.name}\u306f\u30dc\u30fc\u30eb\u3092\u8ffd\u3044\u304b\u3051\u3066\u5f3e\u3044\u305f\u3002`, "observe", { participants: [t] }); - this.effects.push(new Effect("ring", ball.x, ball.y, { size: Math.max(12, ball.r * 0.80), life: 0.20, color: "rgba(170,210,95,0.50)" })); + if (playful && worldRef.relationNotice(t.id, ball.id || "ball", "ball-kick", 16)) worldRef.log(`${t.name}\u306f\u30dc\u30fc\u30eb\u3092\u8ffd\u3044\u304b\u3051\u3066\u5f3e\u3044\u305f\u3002`, "observe", { participants: [t] }); + worldRef.effects.push(new Effect("ring", ball.x, ball.y, { size: Math.max(12, ball.r * 0.80), life: 0.20, color: "rgba(170,210,95,0.50)" })); } } - }).call(worldRef, dt); } function resolveTarinaiHighSpeedCollisions(worldRef, dt) { if (!worldRef) return undefined; - return (function resolveTarinaiHighSpeedCollisionsUnified(dt) { const threshold = PHYSICAL_DAMAGE_SPEED_THRESHOLD; - const now = this.time || 0; - if (!this.tarinaiCollisionMemo) this.tarinaiCollisionMemo = new Map(); - const live = Array.from(this.tarinai || []) - .filter(t => t && !t.dead && !this.isTarinaiHiddenInNestBox(t)) + const now = worldRef.time || 0; + if (!worldRef.tarinaiCollisionMemo) worldRef.tarinaiCollisionMemo = new Map(); + const live = Array.from(worldRef.tarinai || []) + .filter(t => t && !t.dead && !worldRef.isTarinaiHiddenInNestBox(t)) .sort((a, b) => String(a.id || "").localeCompare(String(b.id || ""))); const candidates = []; const maxCandidates = Math.max(24, Math.min(320, live.length * 6)); @@ -310,7 +282,7 @@ const b = live[j]; if (!b || b === a) continue; const key = a.id < b.id ? `${a.id}:${b.id}` : `${b.id}:${a.id}`; - if (now - (this.tarinaiCollisionMemo.get(key) || -999) < 0.42) continue; + if (now - (worldRef.tarinaiCollisionMemo.get(key) || -999) < 0.42) continue; const bvx = b.vx || 0; const bvy = b.vy || 0; const relVx = avx - bvx; @@ -341,14 +313,14 @@ if (candidates.length >= maxCandidates) break; } candidates.sort((p, q) => (p.u - q.u) || (q.score - p.score) || String(p.key).localeCompare(String(q.key))); - const maxImpacts = Math.max(8, Math.min(64, Number(this.maxTarinaiCollisionImpacts || 32) || 32)); + const maxImpacts = Math.max(8, Math.min(64, Number(worldRef.maxTarinaiCollisionImpacts || 32) || 32)); let solved = 0; for (const c of candidates) { if (solved >= maxImpacts) break; const { a, b, key, relSpeed, hitDistance } = c; if (!a || !b || a.dead || b.dead) continue; - if (now - (this.tarinaiCollisionMemo.get(key) || -999) < 0.42) continue; - this.tarinaiCollisionMemo.set(key, now); + if (now - (worldRef.tarinaiCollisionMemo.get(key) || -999) < 0.42) continue; + worldRef.tarinaiCollisionMemo.set(key, now); solved += 1; const as = Math.hypot(a.vx || 0, a.vy || 0); const bs = Math.hypot(b.vx || 0, b.vy || 0); @@ -365,7 +337,7 @@ const dd = Math.hypot(dx, dy); if (dd > 0.001) { nx = dx / dd; ny = dy / dd; } else { - const angle = (typeof stableUnit === "function" ? stableUnit(key, "tarinai-collision-normal") : 0.5) * Math.PI * 2; + const angle = (stableUnit(key, "tarinai-collision-normal")) * Math.PI * 2; nx = Math.cos(angle); ny = Math.sin(angle); } @@ -373,14 +345,14 @@ const overlap = Math.max(0, hitDistance - Math.hypot((a.x || 0) - (b.x || 0), (a.y || 0) - (b.y || 0))); if (overlap > 0.5) { const sep = Math.min(18, overlap * 0.45 + 0.8); - impactor.x = clamp((impactor.x || 0) - nx * sep * 0.45, CONFIG.worldPadding, this.w - CONFIG.worldPadding); - impactor.y = clamp((impactor.y || 0) - ny * sep * 0.45, CONFIG.worldPadding, this.h - CONFIG.worldPadding); - target.x = clamp((target.x || 0) + nx * sep * 0.55, CONFIG.worldPadding, this.w - CONFIG.worldPadding); - target.y = clamp((target.y || 0) + ny * sep * 0.55, CONFIG.worldPadding, this.h - CONFIG.worldPadding); - this.markSpatialDirty?.("tarinai-highspeed-separation"); + impactor.x = clamp((impactor.x || 0) - nx * sep * 0.45, CONFIG.worldPadding, worldRef.w - CONFIG.worldPadding); + impactor.y = clamp((impactor.y || 0) - ny * sep * 0.45, CONFIG.worldPadding, worldRef.h - CONFIG.worldPadding); + target.x = clamp((target.x || 0) + nx * sep * 0.55, CONFIG.worldPadding, worldRef.w - CONFIG.worldPadding); + target.y = clamp((target.y || 0) + ny * sep * 0.55, CONFIG.worldPadding, worldRef.h - CONFIG.worldPadding); + worldRef.markSpatialDirty?.("tarinai-highspeed-separation"); } const damage = physicalDamageFromImpactSpeed(relSpeed, { min: 1.2, max: 22, scale: 26 }); - this.applyImpulse(target, ivx * 0.20 + nx * 28, ivy * 0.20 + ny * 28, { + worldRef.applyImpulse(target, ivx * 0.20 + nx * 28, ivy * 0.20 + ny * 28, { panic: true, target: { x: impactor.x, y: impactor.y }, fearTimer: 0.55, @@ -388,8 +360,8 @@ }); impactor.vx = ivx * 0.72 - nx * Math.min(56, relSpeed * 0.10); impactor.vy = ivy * 0.72 - ny * Math.min(56, relSpeed * 0.10); - this.applyImpactDamage(target, damage, "\u885d\u7a81", { source: impactor }); - if (!impactor.dead) this.applyImpactDamage(impactor, damage * 0.35, "\u885d\u7a81", { source: target }); + worldRef.applyImpactDamage(target, damage, "\u885d\u7a81", { source: impactor }); + if (!impactor.dead) worldRef.applyImpactDamage(impactor, damage * 0.35, "\u885d\u7a81", { source: target }); for (const t of [target, impactor]) { if (!t || t.dead) continue; if (t.enterPanic) t.enterPanic({ target: { x: impactor.x, y: impactor.y }, reason: "\u9ad8\u901f\u3067\u3076\u3064\u304b\u3063\u3066\u6df7\u4e71\u3057\u3066\u3044\u308b", fear: 0.75, wake: true, cause: "tarinai_highspeed_collision" }); @@ -400,22 +372,15 @@ t.fallDir = nx >= 0 ? 1 : -1; t.fearTimer = Math.max(t.fearTimer || 0, 0.55); } - this.effects.push(new Effect("ring", (a.x + b.x) * 0.5, (a.y + b.y) * 0.5, { size: Math.max(16, hitDistance * 0.45), life: 0.20, color: "rgba(210,75,65,0.40)" })); - if (this.relationNotice(target.id, impactor.id, "tarinai-highspeed-collision", 2.6)) this.log(`${target.name}\u306f\u9ad8\u901f\u306e${impactor.name}\u306b\u885d\u7a81\u3057\u3066\u5f3e\u304d\u98db\u3070\u3055\u308c\u305f\u3002`, "accident", { participants: [target, impactor] }); + worldRef.effects.push(new Effect("ring", (a.x + b.x) * 0.5, (a.y + b.y) * 0.5, { size: Math.max(16, hitDistance * 0.45), life: 0.20, color: "rgba(210,75,65,0.40)" })); + if (worldRef.relationNotice(target.id, impactor.id, "tarinai-highspeed-collision", 2.6)) worldRef.log(`${target.name}\u306f\u9ad8\u901f\u306e${impactor.name}\u306b\u885d\u7a81\u3057\u3066\u5f3e\u304d\u98db\u3070\u3055\u308c\u305f\u3002`, "accident", { participants: [target, impactor] }); } - if (solved && this.spatialDirty) this.rebuildSpatial?.(true, "post-tarinai-highspeed-collision"); + if (solved && worldRef.spatialDirty) worldRef.rebuildSpatial?.(true, "post-tarinai-highspeed-collision"); - }).call(worldRef, dt); } global.TarinaiCollisionResponseSystem = Object.freeze({ - PHYSICAL_DAMAGE_SPEED_THRESHOLD, - physicalDamageFromImpactSpeed, - itemHasAttachedLink, - pairKey, - normalizeVector, - separatePair, resolveBallBallCollisions, resolveBallInteractions, resolveTarinaiHighSpeedCollisions, diff --git a/js/colony_situation_system.js b/js/colony_situation_system.js index 6689e4b..79fbc58 100644 --- a/js/colony_situation_system.js +++ b/js/colony_situation_system.js @@ -87,9 +87,8 @@ const id = choose(worldRef, metrics); const next = worldRef.colonyMoodDefinition?.(id) || { id, label: id, effects: { personality: {} } }; worldRef.colonyMood = next; - worldRef.colonySituationMetrics = metrics; worldRef.nextColonyMoodEvalAt = now + 1.25; return next; } - global.TarinaiColonySituationSystem = Object.freeze({ collect, choose, evaluate }); + global.TarinaiColonySituationSystem = Object.freeze({ evaluate }); })(typeof window !== "undefined" ? window : globalThis); diff --git a/js/command_dispatcher.js b/js/command_dispatcher.js index 5d8561a..09742e9 100644 --- a/js/command_dispatcher.js +++ b/js/command_dispatcher.js @@ -26,7 +26,6 @@ if (worldRef.tool === id) return ok({ toolId: id, changed: false }); worldRef.tool = id; if (id !== "shoot") { - worldRef.hoverShootTarget = null; worldRef.shootCursorActive = false; worldRef.shootCursorOffsetX = 0; worldRef.shootCursorOffsetY = 0; @@ -131,7 +130,7 @@ const py = Number(y); if (!Number.isFinite(px) || !Number.isFinite(py)) return fail("invalid_point", { x, y }); if (typeof worldRef.handleClick !== "function") return fail("missing_pointer_handler"); - if (worldRef.tool && worldRef.tool !== "observe") global.TarinaiHistory?.capture?.(worldRef, `tool:${worldRef.tool}`); + if (worldRef.tool && worldRef.tool !== "observe") global.TarinaiHistory.capture(worldRef, `tool:${worldRef.tool}`); worldRef.handleClick(px, py); emit(worldRef, "pointer:primary", { x: px, y: py, toolId: worldRef.tool || "" }); return ok({ x: px, y: py, toolId: worldRef.tool || "" }); @@ -162,11 +161,11 @@ case "pointer.primary": return primaryPointer(worldRef, command.x, command.y); case "history.undo": { - const result = global.TarinaiHistory?.undo?.(worldRef) || { ok: false, reason: "missing_history" }; + const result = global.TarinaiHistory.undo(worldRef); return result.ok ? ok({ action: "undo" }) : fail(result.reason || "undo_failed"); } case "history.redo": { - const result = global.TarinaiHistory?.redo?.(worldRef) || { ok: false, reason: "missing_history" }; + const result = global.TarinaiHistory.redo(worldRef); return result.ok ? ok({ action: "redo" }) : fail(result.reason || "redo_failed"); } default: @@ -175,35 +174,8 @@ } - function installWorldFacade() { - const World = global.World; - if (!World?.prototype || World.prototype.dispatchCommand) return; - Object.defineProperties(World.prototype, Object.getOwnPropertyDescriptors({ - dispatchCommand(command = {}) { return dispatch(this, command); }, - selectToolCommand(toolId) { return selectTool(this, toolId); }, - setSelectionCommand(target) { return setSelection(this, target); }, - clearSelectionCommand() { return clearSelection(this); }, - focusSelectionCommand(target, options = {}) { return focusSelection(this, target, options); }, - primaryPointerCommand(x, y) { return primaryPointer(this, x, y); }, - })); - } - - installWorldFacade(); - global.TarinaiCommands = Object.freeze({ dispatch, - selectTool, setSelection, - clearSelection, - focusSelection, - toggleSelectedFavorite, - setToolSize, - cycleToolSize, - setPaused, - togglePaused, - cycleSpeed, - primaryPointer, - undo(worldRef = global.world) { return global.TarinaiHistory?.undo?.(worldRef) || fail("missing_history"); }, - redo(worldRef = global.world) { return global.TarinaiHistory?.redo?.(worldRef) || fail("missing_history"); }, }); })(typeof window !== "undefined" ? window : globalThis); diff --git a/js/constraint_system.js b/js/constraint_system.js index dda2c6e..d9745f1 100644 --- a/js/constraint_system.js +++ b/js/constraint_system.js @@ -10,7 +10,6 @@ function ls(item, key, fallback = 0) { return bodyApi()?.linkScalar?.(item, key, fallback) ?? fallback; } function lset(item, key, value) { return bodyApi()?.setLinkScalar?.(item, key, value) || false; } function ep(item, index) { return bodyApi()?.endpoint?.(item, index) || null; } - function setEp(item, index, value) { return bodyApi()?.setEndpoint?.(item, index, value) || false; } function parts(item) { return bodyApi()?.particles?.(item) || null; } function setParts(item, list) { return bodyApi()?.setParticles?.(item, list) || false; } function ensureConstraint(item, worldRef = null, opts = {}) { @@ -26,7 +25,7 @@ return c; } function commitMechanicalBody(item, reason = "constraint-moved-body") { - if (!item || !global.TarinaiMechanicalSystem?.isMechanicalType?.(item.type)) return false; + if (!item || !global.TarinaiMechanicalSystem.isMechanicalType(item.type)) return false; // Constraint pulls sometimes mutate item.x/y as a scratch pose. Preserve // that pose in physicsBody before normalizing, otherwise normalizeBody() // can restore the old body pose and silently cancel the correction. @@ -74,7 +73,7 @@ item._lastConstraintSpatialDirtyY = y; item._lastConstraintSpatialDirtyR = r; item._nextConstraintSpatialDirtyAt = now + Math.max(0.016, Number(minInterval || 0.065) || 0.065); - const marked = global.TarinaiPhysicsProjectionSystem?.markSpatialDirty?.(world, item, reason, threshold, minInterval) ?? false; + const marked = global.TarinaiPhysicsProjectionSystem.markSpatialDirty(world, item, reason, threshold, minInterval) ?? false; if (!marked) world.markSpatialDirty?.(reason); if (world.constraintDirtyStatsThisFrame) world.constraintDirtyStatsThisFrame.marked = (world.constraintDirtyStatsThisFrame.marked || 0) + 1; return true; @@ -84,11 +83,11 @@ } function mechanicalPowered(item) { - return Boolean(global.TarinaiMechanicalSystem?.isPowered?.(item)); + return Boolean(global.TarinaiMechanicalSystem.isPowered(item)); } function commitConstraintMovedMechanical(item, reason = "constraint-projection") { - if (!item || !global.TarinaiMechanicalSystem?.isMechanicalType?.(item.type)) return false; + if (!item || !global.TarinaiMechanicalSystem.isMechanicalType(item.type)) return false; bodyApi()?.syncPoseFromItem?.(item); bodyApi()?.normalizeBodyState?.(item, item.physicsBody); bodyApi()?.markBodyChanged?.(item, reason); @@ -97,9 +96,9 @@ return true; } function reciprocatorAxis(item) { - if (global.TarinaiMechanicalSystem?.axis) return global.TarinaiMechanicalSystem.axis(item); - const fallback = typeof itemAngleFor === "function" ? itemAngleFor(item) : (Number(item?.angle) || 0); - const a = typeof normalizedItemAngle === "function" ? normalizedItemAngle(ps(item, "railAxis", fallback), fallback) : ps(item, "railAxis", fallback); + if (global.TarinaiMechanicalSystem.axis) return global.TarinaiMechanicalSystem.axis(item); + const fallback = itemAngleFor(item); + const a = normalizedItemAngle(ps(item, "railAxis", fallback), fallback); return { x: Math.cos(a), y: Math.sin(a), angle: a }; } @@ -112,15 +111,6 @@ if (!Number.isFinite(Number(ps(item, "railAnchorY", NaN)))) pset(item, "railAnchorY", item.y || 0, "constraint-anchor-init"); } - function railPhaseFromPosition(item) { - resetReciprocatorAnchorIfMissing(item); - const axis = reciprocatorAxis(item); - const travel = reciprocatorHalfTravel(item); - const dx = (Number(item.x || 0) || 0) - ps(item, "railAnchorX", 0); - const dy = (Number(item.y || 0) || 0) - ps(item, "railAnchorY", 0); - return clamp((dx * axis.x + dy * axis.y) / Math.max(10, travel), -1, 1); - } - function resolveEndpointObject(endpoint, worldRef) { if (!endpoint || !worldRef) return null; const allowFuzzy = endpoint.fuzzyResolve === true || endpoint.fuzzyFallback === true; @@ -164,11 +154,11 @@ } function itemAngle(item) { - return typeof itemAngleFor === "function" ? itemAngleFor(item) : (Number(item?.angle) || 0); + return itemAngleFor(item); } function isMechanicalLinkTarget(item) { - return Boolean(item && !item.dead && global.TarinaiMechanicalSystem?.isMechanicalType?.(item.type)); + return Boolean(item && !item.dead && global.TarinaiMechanicalSystem.isMechanicalType(item.type)); } function localToWorld(item, lx, ly) { @@ -190,7 +180,7 @@ function nearestMechanicalLocalPoint(item, lx, ly) { if (!isMechanicalLinkTarget(item)) return null; - const segments = global.TarinaiMechanicalSystem?.sanitizeSegments?.(item) || []; + const segments = global.TarinaiMechanicalSystem.sanitizeSegments(item) || []; let best = null; for (const seg of segments) { if (!Array.isArray(seg) || seg.length < 4) continue; @@ -215,7 +205,7 @@ function mechanicalLocalPointSupported(item, lx, ly, endpoint = null) { if (!isMechanicalLinkTarget(item)) return true; - const version = Number(item?._mechanicalShapeVersion || item?._segmentsVersion || 0) || 0; + const version = Number(item?._mechanicalShapeVersion || 0) || 0; if (endpoint && endpoint._supportItemId === item.id && endpoint._supportCheckedVersion === version) return endpoint._supportAlive !== false; const nearest = nearestMechanicalLocalPoint(item, lx, ly); const alive = Boolean(nearest && nearest.d <= mechanicalSupportTolerance(item)); @@ -339,7 +329,7 @@ if (obj.type === "ball") return 0.7; if (obj.type === "zunchi") return 0.55; if (obj.type === "rope" || obj.type === "rod") return 1.6; - if (global.TarinaiMechanicalSystem?.isMechanicalType?.(obj.type)) { + if (global.TarinaiMechanicalSystem.isMechanicalType(obj.type)) { if (obj.type === "rotator") return Infinity; // anchored pivot: links may torque it, not translate it if (obj.type === "reciprocator") return mechanicalPowered(obj) ? 18.0 : 5.8; if (obj.type === "poison_block") return Math.max(0.35, ps(obj, "mass", 0.45)); @@ -371,7 +361,7 @@ obj.x += moveX; obj.y += moveY; if (obj.state === "sleep" || obj.sleeping) { - global.TarinaiMovementUpdateStep?.markSleepExternalMotion?.(obj, dt, `constraint-${mode}`); + global.TarinaiMovementUpdateStep.markSleepExternalMotion(obj, dt, `constraint-${mode}`); obj._sleepExternalMotionUntil = Math.max(Number(obj._sleepExternalMotionUntil || 0) || 0, (Number(obj.world?.time || 0) || 0) + (mode === "rod" ? 1.1 : 0.6)); } obj.target = null; @@ -417,7 +407,7 @@ obj.x = beforeX + px; obj.y = beforeY + py; commitConstraintMovedMechanical(obj, "constraint-poison-project"); - global.TarinaiMechanicalSystem?.wakeItem?.(obj, "constraint-poison-project"); + global.TarinaiMechanicalSystem.wakeItem(obj, "constraint-poison-project"); return true; } if (obj.type === "rotator") { @@ -441,51 +431,6 @@ return true; } - function updateFlexibleMidParticle(item, a, b, dt, length) { - if (!a || !b || item.type !== "rope") return false; - const ax = a.x, ay = a.y, bx = b.x, by = b.y; - const dx = bx - ax, dy = by - ay; - const d = Math.max(0.001, Math.hypot(dx, dy)); - if (!Number.isFinite(ls(item, "midX", NaN)) || !Number.isFinite(ls(item, "midY", NaN))) { - lset(item, "midX", (ax + bx) * 0.5); - lset(item, "midY", (ay + by) * 0.5 + Math.max(4, (length - d) * 0.25)); - lset(item, "midVx", 0); - lset(item, "midVy", 0); - } - const prevX = ls(item, "midX", item.x || 0); - const prevY = ls(item, "midY", item.y || 0); - const step = Math.max(0.008, Math.min(0.05, Number(dt || 0.016) || 0.016)); - lset(item, "midVx", ls(item, "midVx", 0) * Math.pow(0.62, step * 60)); - lset(item, "midVy", ls(item, "midVy", 0) * Math.pow(0.62, step * 60) + 90 * step); - lset(item, "midX", ls(item, "midX", 0) + ls(item, "midVx", 0) * step); - lset(item, "midY", ls(item, "midY", 0) + ls(item, "midVy", 0) * step); - const half = Math.max(12, length * 0.5); - if (d >= length * 0.985) { - lset(item, "midX", (ax + bx) * 0.5); - lset(item, "midY", (ay + by) * 0.5); - lset(item, "midVx", ls(item, "midVx", 0) * 0.25); - lset(item, "midVy", ls(item, "midVy", 0) * 0.25); - } else { - for (let i = 0; i < 4; i += 1) { - for (const pnt of [[ax, ay], [bx, by]]) { - const vx = ls(item, "midX", 0) - pnt[0]; - const vy = ls(item, "midY", 0) - pnt[1]; - const dd = Math.max(0.001, Math.hypot(vx, vy)); - if (dd > half) { - lset(item, "midX", pnt[0] + vx / dd * half); - lset(item, "midY", pnt[1] + vy / dd * half); - } - } - } - const minY = Math.min(ay, by) - length * 0.35; - const maxY = Math.max(ay, by) + length * 0.70; - lset(item, "midY", clamp(ls(item, "midY", 0), minY, maxY)); - } - lset(item, "midVx", clamp((ls(item, "midX", 0) - prevX) / step, -220, 220)); - lset(item, "midVy", clamp((ls(item, "midY", 0) - prevY) / step, -220, 220)); - return Math.hypot(ls(item, "midX", 0) - prevX, ls(item, "midY", 0) - prevY) > 0.01; - } - function applyDistanceConstraint(item, a, b, dt, length, mode) { const solveOnce = (pa, pb, strength, maxFraction) => { if (!pa || !pb || !pa.obj || !pb.obj) return false; @@ -541,7 +486,7 @@ } function ropeLodSettings(worldRef, length) { - const tier = global.TarinaiPerf?.renderQualityTier?.() || "high"; + const tier = global.TarinaiPerf.renderQualityTier() || "high"; const ropeCount = Number(worldRef?.itemCounts?.rope || 0) || 0; let spacing = 34; let maxParticles = 24; @@ -594,13 +539,7 @@ } function rectLocalPointForConstraint(r, x, y) { - const shared = global.TarinaiCollisionFootprints?.rectLocalPoint; - if (typeof shared === "function") return shared(r, x, y); - const dx = x - (r.cx ?? 0); - const dy = y - (r.cy ?? 0); - const c = Number.isFinite(r.cos) ? r.cos : Math.cos(r.angle || 0); - const s = Number.isFinite(r.sin) ? r.sin : Math.sin(r.angle || 0); - return { x: dx * c + dy * s, y: -dx * s + dy * c }; + return global.TarinaiCollisionFootprints.rectLocalPoint(r, x, y); } function rectWorldNormalForConstraint(r, nx, ny) { @@ -932,9 +871,9 @@ } let stamp = `${endpoint?.kind || "item"}:${obj.id || "?"}:${obj.type || (obj.name ? "tarinai" : "entity")}:${q(obj.x)}:${q(obj.y)}`; if (obj.radius && obj.name) stamp += `:${q(obj.vx, 4)}:${q(obj.vy, 4)}:${obj.state || ""}`; - else if (global.TarinaiMechanicalSystem?.isMechanicalType?.(obj.type)) { - const a = typeof itemAngleFor === "function" ? itemAngleFor(obj) : (Number(obj.angle) || 0); - const shape = Number(obj._mechanicalShapeVersion || obj._segmentsVersion || 0) || 0; + else if (global.TarinaiMechanicalSystem.isMechanicalType(obj.type)) { + const a = itemAngleFor(obj); + const shape = Number(obj._mechanicalShapeVersion || 0) || 0; stamp += `:${q(a, 1000)}:${shape}:${ps(obj, "motorOn", true) !== false ? 1 : 0}:${ps(obj, "railOn", true) !== false ? 1 : 0}:${q(obj.vx, 4)}:${q(obj.vy, 4)}:${q(ps(obj, "spin", 0), 1000)}:${q(ps(obj, "slideSpeed", 0), 4)}`; } else stamp += `:${q(obj.vx, 4)}:${q(obj.vy, 4)}:${Number(obj.amount || 0) > 0 ? 1 : 0}`; if (Number.isFinite(Number(endpoint?.localX)) || Number.isFinite(Number(endpoint?.localY))) stamp += `:l${q(endpoint.localX)}:${q(endpoint.localY)}`; @@ -1004,7 +943,7 @@ function updateWorld(worldRef, dt = 0.016, opts = {}) { if (!worldRef?.itemsOfType) return 0; const profiler = global.TarinaiPerf; - const end = profiler?.begin?.("update.constraints") || null; + const end = profiler.begin("update.constraints") || null; try { worldRef.ensureItemBuckets?.("constraint-world"); const maxLinks = Math.max(8, Number(opts.maxLinks || 160) || 160); @@ -1069,17 +1008,9 @@ } const api = Object.freeze({ - endpointWorld, - resolveEndpointObject, - endpointMass, - moveEndpointObject, - snapEndpointToTarget, - remapLinksForEditedMechanicalItem, updateFlexibleLink, updateRigidLink, - updateLink, updateWorld, - pointSegmentDistance, }); global.TarinaiConstraintSystem = api; diff --git a/js/data.js b/js/data.js index 0c15ec3..137665a 100644 --- a/js/data.js +++ b/js/data.js @@ -34,65 +34,8 @@ const SPRITES = [ { id: "sunbath_2", label: "\u65e5\u5149\u6d74\u305f\u308a\u306a\u3044 2", path: "assets/sprites/tarinai_32_sunbath_02.webp", actionOnly: true }, { id: "sunbath_3", label: "\u65e5\u5149\u6d74\u305f\u308a\u306a\u3044 3", path: "assets/sprites/tarinai_33_sunbath_03.webp", actionOnly: true }, ]; -const DECOR_ASSETS = [ - { id: "zunchi", path: "assets/objects/zunchi.webp" }, - { id: "zunchi_02", path: "assets/objects/zunchi_02.webp" }, - { id: "ant_queen", path: "assets/objects/ant_queen.webp" }, - { id: "ant_worker", path: "assets/objects/ant_worker.webp" }, - { id: "genkotsu", path: "assets/objects/genkotsu.webp" }, - { id: "pushpin", path: "assets/objects/pushpin.webp" }, - { id: "pushpin_stuck", path: "assets/objects/pushpin_stuck.webp" }, - { id: "oshibyo", path: "assets/objects/oshibyo.webp" }, - { id: "oshibyo_stuck", path: "assets/objects/oshibyo_stuck.webp" }, - { id: "plushie_bear", path: "assets/objects/plushie_bear.png" }, -]; - -const ALPHA_BOUNDS = { - smile: { x: 18, y: 18, w: 476, h: 432, imageW: 512, imageH: 468 }, - angry: { x: 16, y: 16, w: 480, h: 432, imageW: 512, imageH: 464 }, - teary: { x: 17, y: 17, w: 478, h: 438, imageW: 512, imageH: 472 }, - jito: { x: 18, y: 18, w: 476, h: 451, imageW: 512, imageH: 487 }, - drool: { x: 18, y: 18, w: 476, h: 425, imageW: 512, imageH: 461 }, - cry: { x: 15, y: 15, w: 482, h: 448, imageW: 512, imageH: 478 }, - sleep: { x: 18, y: 18, w: 475, h: 439, imageW: 511, imageH: 475 }, - pokan: { x: 18, y: 18, w: 476, h: 433, imageW: 512, imageH: 469 }, - stretch: { x: 15, y: 15, w: 482, h: 354, imageW: 512, imageH: 384 }, - hurt: { x: 17, y: 21, w: 478, h: 432, imageW: 512, imageH: 468 }, - weak: { x: 53, y: 18, w: 405, h: 435, imageW: 512, imageH: 468 }, - back: { x: 17, y: 21, w: 478, h: 432, imageW: 512, imageH: 468 }, - flee: { x: 17, y: 21, w: 478, h: 432, imageW: 512, imageH: 468 }, - zunda_eat: { x: 17, y: 21, w: 478, h: 432, imageW: 512, imageH: 468 }, - hurt2: { x: 16, y: 20, w: 480, h: 434, imageW: 512, imageH: 468 }, - normal_smirk: { x: 16, y: 20, w: 480, h: 434, imageW: 512, imageH: 468 }, - normal_tongue: { x: 16, y: 20, w: 480, h: 434, imageW: 512, imageH: 468 }, - fear: { x: 16, y: 20, w: 480, h: 434, imageW: 512, imageH: 468 }, - flee_fear2: { x: 16, y: 26, w: 480, h: 428, imageW: 512, imageH: 468 }, - sleep2: { x: 16, y: 21, w: 480, h: 433, imageW: 512, imageH: 468 }, - normal_happy: { x: 16, y: 21, w: 480, h: 433, imageW: 512, imageH: 468 }, - fear_blue: { x: 16, y: 21, w: 480, h: 433, imageW: 512, imageH: 468 }, - fear_cry: { x: 16, y: 21, w: 480, h: 433, imageW: 512, imageH: 468 }, - stress_dizzy: { x: 16, y: 21, w: 480, h: 433, imageW: 512, imageH: 468 }, - stress_sweat: { x: 16, y: 26, w: 480, h: 428, imageW: 512, imageH: 468 }, - intimidate: { x: 98, y: 16, w: 316, h: 438, imageW: 512, imageH: 468 }, - zunchi_slave: { x: 17, y: 22, w: 478, h: 431, imageW: 512, imageH: 468 }, - hungry_70: { x: 0, y: 0, w: 430, h: 366, imageW: 430, imageH: 366 }, - zunchi_slave_alt: { x: 42, y: 22, w: 425, h: 426, imageW: 512, imageH: 468 }, - zunchi: { x: 0, y: 0, w: 236, h: 178, imageW: 236, imageH: 178 }, - zunchi_02: { x: 8, y: 63, w: 220, h: 105, imageW: 236, imageH: 178 }, - ant_queen: { x: 0, y: 0, w: 300, h: 155, imageW: 300, imageH: 155 }, - ant_worker: { x: 0, y: 0, w: 140, h: 72, imageW: 140, imageH: 72 }, - genkotsu: { x: 8, y: 8, w: 220, h: 283, imageW: 236, imageH: 299 }, - sunbath_1: { x: 71, y: 18, w: 369, h: 432, imageW: 512, imageH: 468 }, - sunbath_2: { x: 69, y: 18, w: 374, h: 432, imageW: 512, imageH: 468 }, - sunbath_3: { x: 71, y: 18, w: 369, h: 432, imageW: 512, imageH: 468 }, - plushie_bear: { x: 0, y: 0, w: 150, h: 192, imageW: 150, imageH: 192 }, -}; - -const NEEDS = ["\u98df\u3079\u7269", "\u4ef2\u9593", "\u7720\u308a", "\u52c7\u6c17", "\u6c34", "\u9759\u3051\u3055", "\u3042\u305f\u305f\u304b\u3055", "\u610f\u5473", "\u7a7a\u9593", "\u81ea\u4fe1"]; - const CONFIG = { initialPopulation: 18, - maxPopulation: Number.MAX_SAFE_INTEGER, maxFenceCollisionChecks: 18, effectLimit: 78, grassLimit: 99, @@ -101,7 +44,6 @@ const CONFIG = { hungerPerMinute: 36.0, lonelinessPerMinute: 7.6, energyPerMinute: 8.2, - stressDecayPerMinute: 10.2, reproductionCooldown: 18, dayLength: 120, childGrowTime: 58, @@ -110,13 +52,17 @@ const CONFIG = { weatherChangeMin: 46, weatherChangeMax: 96, bubbleLimit: 32, - highPopulationMode: 70, - ultraPopulationMode: 120, }; +const FIELD_LAYOUT = Object.freeze({ + height: "calc(100vh - 140px)", + minHeight: "620px", + mobileHeight: "66vh", + mobileMinHeight: "460px", +}); const FIELD_TYPES = { - cage: { id: "cage", label: "\u30b1\u30fc\u30b8", sizeLabel: "\u5c0f", height: "calc(100vh - 140px)", minHeight: "620px", mobileHeight: "66vh", mobileMinHeight: "460px", worldScale: 0.46, population: 12, grass: 16 }, - garden: { id: "garden", label: "\u5ead", sizeLabel: "\u4e2d", height: "calc(100vh - 140px)", minHeight: "620px", mobileHeight: "66vh", mobileMinHeight: "460px", worldScale: 1.0, population: CONFIG.initialPopulation, grass: 34 }, - park: { id: "park", label: "\u516c\u5712", sizeLabel: "\u5927", height: "calc(100vh - 140px)", minHeight: "620px", mobileHeight: "66vh", mobileMinHeight: "460px", worldScale: 2.35, population: 24, grass: 62 }, + cage: { id: "cage", worldScale: 0.46, population: 12, grass: 16 }, + garden: { id: "garden", worldScale: 1.0, population: CONFIG.initialPopulation, grass: 34 }, + park: { id: "park", worldScale: 2.35, population: 24, grass: 62 }, }; const LIGHTING_TUNING = { rayAlpha: 0.040, diff --git a/js/debug_tools.js b/js/debug_tools.js index ea60846..a05a307 100644 --- a/js/debug_tools.js +++ b/js/debug_tools.js @@ -82,8 +82,8 @@ let lastEvents = []; let lastAudio = []; - window.TarinaiEvents?.on("*", ev => { lastEvents.unshift(ev.type); if (lastEvents.length > 10) lastEvents.length = 10; }); - window.TarinaiEvents?.on("audio:play", ev => { lastAudio.unshift(`${ev.detail?.id || "?"}:${ev.detail?.category || "?"}`); if (lastAudio.length > 8) lastAudio.length = 8; }); + window.TarinaiEvents.on("*", ev => { lastEvents.unshift(ev.type); if (lastEvents.length > 10) lastEvents.length = 10; }); + window.TarinaiEvents.on("audio:play", ev => { lastAudio.unshift(`${ev.detail?.id || "?"}:${ev.detail?.category || "?"}`); if (lastAudio.length > 8) lastAudio.length = 8; }); function fmtMs(v) { const n = Number(v || 0); @@ -134,11 +134,11 @@ const DEBUG_REFRESH_MS = 750; window.setInterval(() => { const w = window.world; - const input = window.TarinaiInputMode?.snapshot?.() || {}; + const input = window.TarinaiInputMode.snapshot() || {}; const diag = w?.lastRuntimeDiagnostics || w?.runtimeDiagnostics?.() || {}; const behaviorCounts = diag.behavior?.counts || {}; const topBehaviors = topMap(behaviorCounts, 5); - const perf = diag.perf || window.TarinaiPerf?.snapshot?.() || {}; + const perf = diag.perf || window.TarinaiPerf.snapshot() || {}; const metrics = perf.metrics || {}; const currentDpr = (typeof uiCache !== "undefined" && uiCache) ? uiCache.canvasDpr : "?"; const creature = diag.creatures || {}; @@ -165,7 +165,7 @@ `item buckets:${diag.items?.bucketTypes ?? 0} dirty:${Boolean(diag.items?.bucketsDirty)} id-map:${diag.items?.idMapSize ?? 0} bucketRebuilds:${diag.items?.bucketRebuildsTotal ?? 0}`, `dpr ${currentDpr} cache ${window.TARINAI_APP?.cacheName || "?"} sw ${navigator.serviceWorker?.controller ? "controlled" : "uncontrolled"}`, `input ${input.currentMode || "?"} touchFirst:${Boolean(input.touchFirst)} coarse:${Boolean(input.pointerCoarse)} hover:${Boolean(input.hasHover)} compact:${Boolean(input.isCompactViewport)}`, - `audio ${lastAudio.join(", ") || window.TarinaiAudio?.lastPlayedId || "-"}`, + `audio ${lastAudio.join(", ") || window.TarinaiAudio.lastPlayedId || "-"}`, `events ${lastEvents.join(", ")}`, ]; lastText = lines.join("\n"); diff --git a/js/deterministic_helpers.js b/js/deterministic_helpers.js index 93deb9f..fd6310b 100644 --- a/js/deterministic_helpers.js +++ b/js/deterministic_helpers.js @@ -32,7 +32,6 @@ global.deterministicSigned = global.deterministicSigned || ((worldRef = null, salt = "", ...parts) => global.deterministicUnit(worldRef, salt, ...parts) < 0.5 ? -1 : 1); global.deterministicChance = global.deterministicChance || ((worldRef = null, salt = "", chance = 0, ...parts) => { const p = clamp01(chance); return p >= 1 || (p > 0 && global.deterministicUnit(worldRef, salt, ...parts) < p); }); global.deterministicAngle = global.deterministicAngle || ((worldRef = null, salt = "", ...parts) => global.deterministicRange(worldRef, salt, 0, Math.PI * 2, ...parts)); - global.TarinaiDeterministicHelpers = Object.freeze({ keyPart, hashUnit }); })(typeof window !== "undefined" ? window : globalThis); // Keep the historical bare helper names available to legacy scripts while the diff --git a/js/disease_registry.js b/js/disease_registry.js index ca63c50..5bd4e69 100644 --- a/js/disease_registry.js +++ b/js/disease_registry.js @@ -5,59 +5,34 @@ disease_zunchi: { id: "disease_zunchi", flag: "zunchiDisease", - label: "\u305a\u3093\u3061\u75c5", - symptoms: ["stress", "energy_loss", "infection"], - displayText: "\u305a\u3093\u3061\u75c5", - color: "rgba(113,93,59,0.74)", mysteryEligible: true, - cureRules: ["water", "natural_recovery"] }, disease_sleep: { id: "disease_sleep", flag: "sleepDisease", - label: "\u306d\u3080\u308a\u75c5", - symptoms: ["sleep_lock", "no_counterattack"], - displayText: "\u306d\u3080\u308a\u75c5", - color: "rgba(104,84,168,0.78)", mysteryEligible: true, - cureRules: ["damage"] }, disease_explosion: { id: "disease_explosion", flag: "explosionDisease", - label: "\u7206\u767a\u75c5", - symptoms: ["explodes_after_timer", "water_seeking"], - displayText: "\u7206\u767a\u75c5", - color: "rgba(210,90,44,0.78)", baseInfectionChance: 0.15, mysteryEligible: true, - cureRules: ["water", "zunda_mochi"] }, disease_fight: { id: "disease_fight", flag: "fightDisease", - label: "\u304d\u305a\u3064\u304d\u75c5", - symptoms: ["fight_lash_out", "zunda_seeking"], - displayText: "\u304d\u305a\u3064\u304d\u75c5", - color: "rgba(180,86,52,0.80)", mysteryEligible: true, - cureRules: ["zunda_mochi", "natural_recovery"] } }; class DiseaseRegistry extends global.TarinaiRegistryBase { - byFlag(flag = "") { return [...this.defs.values()].find(def => def.flag === flag) || null; } - label(id = "") { return super.label(id, id || ""); } - color(id = "") { return super.color(id, "rgba(255,242,160,0.85)"); } infectionChance(id = "", fallback = 0) { const value = this.get(id)?.baseInfectionChance; return Number.isFinite(value) ? value : fallback; } mysteryPool() { return [...this.defs.values()].filter(def => def.mysteryEligible).map(def => def.id); } - debugSnapshot() { return super.debugSnapshot((id, def) => ({ id: def.id || id, label: def.label, flag: def.flag, cures: def.cureRules || [] })); } } const registry = new DiseaseRegistry(rawDefinitions); global.TarinaiDiseaseRegistry = registry; - global.TARINAI_DISEASE_DEFINITIONS = Object.freeze(rawDefinitions); })(typeof window !== "undefined" ? window : globalThis); diff --git a/js/event_bus.js b/js/event_bus.js index 94d9322..562dab0 100644 --- a/js/event_bus.js +++ b/js/event_bus.js @@ -1,10 +1,8 @@ "use strict"; class TarinaiEventBus { - constructor({ historyLimit = 180 } = {}) { + constructor() { this.handlers = new Map(); - this.history = []; - this.historyLimit = historyLimit; } on(type, handler) { @@ -28,8 +26,6 @@ class TarinaiEventBus { emit(type, detail = {}) { if (!type) return null; const event = { type, detail: detail || {}, at: performance?.now?.() ?? Date.now() }; - this.history.unshift(event); - if (this.history.length > this.historyLimit) this.history.length = this.historyLimit; const direct = this.handlers.get(type); if (direct) for (const handler of [...direct]) this.safeCall(handler, event); const wildcard = this.handlers.get("*"); @@ -41,12 +37,6 @@ class TarinaiEventBus { try { handler(event); } catch (err) { console.error("event handler failed", event.type, err); } } - - recent(filter = null, limit = 40) { - const list = filter ? this.history.filter(ev => ev.type === filter) : this.history; - return list.slice(0, limit); - } } -window.TarinaiEventBus = TarinaiEventBus; window.TarinaiEvents = window.TarinaiEvents || new TarinaiEventBus(); diff --git a/js/family_graph.js b/js/family_graph.js index 5534f89..7552b23 100644 --- a/js/family_graph.js +++ b/js/family_graph.js @@ -45,5 +45,5 @@ return comps; } - global.TarinaiFamilyGraph = { buildGraph, connectedComponents }; + global.TarinaiFamilyGraph = { connectedComponents }; })(typeof window !== "undefined" ? window : globalThis); diff --git a/js/ground_types.js b/js/ground_types.js index 5e7cf39..fc5c70f 100644 --- a/js/ground_types.js +++ b/js/ground_types.js @@ -24,10 +24,6 @@ function groundLabel(id = "soil") { return groundDefinition(id).label || "\u7802"; } -function groundDescription(id = "soil") { - return groundDefinition(id).description || ""; -} - function normalizedGroundStressMultiplier(id = "soil") { const mult = Number(groundDefinition(id).stressMultiplier); return Number.isFinite(mult) && mult > 0 ? mult : 1; @@ -72,12 +68,9 @@ function groundTooltipText(id = "soil") { } window.TarinaiGround = Object.freeze({ - definitions: GROUND_TYPES, - order: GROUND_TYPE_ORDER, definition: groundDefinition, exists: groundExists, label: groundLabel, - description: groundDescription, stressMultiplier: normalizedGroundStressMultiplier, grassMultiplier: normalizedGroundGrassMultiplier, grassGrowthMultiplier: normalizedGroundGrassGrowthMultiplier, diff --git a/js/health.js b/js/health.js index 20c413a..52ec786 100644 --- a/js/health.js +++ b/js/health.js @@ -78,7 +78,6 @@ const HEALTH = (() => { t.lastDamageAt = now; if (t.lastDamageAmount >= 6 || t.lastDamageAmount >= Math.max(4, (t.energy || 0) * 0.22)) { t.lastMajorDamageCause = cause; - t.lastMajorDamageAmount = t.lastDamageAmount; t.lastMajorDamageAt = now; } return cause; @@ -207,7 +206,6 @@ const HEALTH = (() => { if (total < maxEnergy * 0.30) return false; const majorCause = causeLabel(dominant.cause || cause) || causeLabel(cause) || CAUSES.accident; t.lastMajorDamageCause = majorCause; - t.lastMajorDamageAmount = total; t.lastMajorDamageAt = now; if (now < Number(t.nextMajorDamageVoiceAt || -999)) return false; t.nextMajorDamageVoiceAt = now + 1.0; @@ -298,10 +296,6 @@ const HEALTH = (() => { } return Object.freeze({ - MAJOR_DAMAGE_WINDOW, - WEAKENED_TRACE_WINDOW, - CAUSES, - causeLabel, recentDamageCause, rememberDamage, weakenedDeathReasonFor, @@ -309,8 +303,6 @@ const HEALTH = (() => { normalizeDeathReason, applyDamage, briefDeathReason, - maybeReleaseZunchiFromDamage, - maybeTriggerMajorDamageReaction, }); })(); diff --git a/js/history_system.js b/js/history_system.js index e715499..41a8df5 100644 --- a/js/history_system.js +++ b/js/history_system.js @@ -30,7 +30,7 @@ function capture(worldRef = global.world, label = "action") { const world = ensure(worldRef); if (!world || world._historyRestoring) return false; - const snap = global.TarinaiSnapshot?.createSnapshot?.(world); + const snap = global.TarinaiSnapshot.createSnapshot(world); if (!snap) return false; const key = snapshotKey(snap); if (key && key === world._lastUndoSnapshotKey) return false; @@ -42,11 +42,11 @@ } function restore(worldRef, entry) { - if (!worldRef || !entry?.snapshot || !global.TarinaiRestoreCoordinator?.restoreSnapshot) return false; + if (!worldRef || !entry?.snapshot) return false; worldRef._historyRestoring = true; try { global.TarinaiRestoreCoordinator.restoreSnapshot(cloneSnapshot(entry.snapshot), worldRef, { syncUi: true }); - worldRef._lastUndoSnapshotKey = snapshotKey(global.TarinaiSnapshot?.createSnapshot?.(worldRef)); + worldRef._lastUndoSnapshotKey = snapshotKey(global.TarinaiSnapshot.createSnapshot(worldRef)); return true; } finally { worldRef._historyRestoring = false; @@ -56,7 +56,7 @@ function undo(worldRef = global.world) { const world = ensure(worldRef); if (!world || !world._undoStack.length) return { ok: false, reason: "empty" }; - const current = global.TarinaiSnapshot?.createSnapshot?.(world); + const current = global.TarinaiSnapshot.createSnapshot(world); const prev = world._undoStack.pop(); if (current) world._redoStack.push({ snapshot: cloneSnapshot(current), key: snapshotKey(current), label: "redo", time: world.time || 0 }); if (world._redoStack.length > MAX_HISTORY) world._redoStack.splice(0, world._redoStack.length - MAX_HISTORY); @@ -67,7 +67,7 @@ function redo(worldRef = global.world) { const world = ensure(worldRef); if (!world || !world._redoStack.length) return { ok: false, reason: "empty" }; - const current = global.TarinaiSnapshot?.createSnapshot?.(world); + const current = global.TarinaiSnapshot.createSnapshot(world); const next = world._redoStack.pop(); if (current) world._undoStack.push({ snapshot: cloneSnapshot(current), key: snapshotKey(current), label: "undo", time: world.time || 0 }); if (world._undoStack.length > MAX_HISTORY) world._undoStack.splice(0, world._undoStack.length - MAX_HISTORY); @@ -75,17 +75,6 @@ return { ok, label: next.label || "" }; } - function clear(worldRef = global.world) { - const world = ensure(worldRef); - if (!world) return false; - world._undoStack.length = 0; - world._redoStack.length = 0; - world._lastUndoSnapshotKey = ""; - return true; - } - function canUndo(worldRef = global.world) { return !!worldRef?._undoStack?.length; } - function canRedo(worldRef = global.world) { return !!worldRef?._redoStack?.length; } - - global.TarinaiHistory = Object.freeze({ capture, undo, redo, clear, canUndo, canRedo }); + global.TarinaiHistory = Object.freeze({ capture, undo, redo }); })(typeof window !== "undefined" ? window : globalThis); diff --git a/js/impact_core_system.js b/js/impact_core_system.js index 8991aa2..66b521c 100644 --- a/js/impact_core_system.js +++ b/js/impact_core_system.js @@ -46,7 +46,6 @@ global.TarinaiImpactCoreSystem = Object.freeze({ PHYSICAL_DAMAGE_SPEED_THRESHOLD, physicalDamageFromImpactSpeed, - linkEndpointMatchesItem, itemHasAttachedLink, }); })(typeof window !== "undefined" ? window : globalThis); diff --git a/js/impact_response_system.js b/js/impact_response_system.js index 94a9e10..10bd231 100644 --- a/js/impact_response_system.js +++ b/js/impact_response_system.js @@ -1,14 +1,13 @@ "use strict"; (function (global) { - const ImpactCore = global.TarinaiImpactCoreSystem || {}; + const ImpactCore = global.TarinaiImpactCoreSystem; const PHYSICAL_DAMAGE_SPEED_THRESHOLD = ImpactCore.PHYSICAL_DAMAGE_SPEED_THRESHOLD || 165; const physicalDamageFromImpactSpeed = ImpactCore.physicalDamageFromImpactSpeed; const itemHasAttachedLink = ImpactCore.itemHasAttachedLink; function itemDropImpact(worldRef, it) { if (!worldRef) return undefined; - return (function itemDropImpactUnified(it) { if (!it || it.dead || it.type === "water") return; const isStone = it.type === "stone"; const isGenkotsu = it.type === "genkotsu"; @@ -19,17 +18,17 @@ const damage = physicalDamageFromImpactSpeed(impactSpeed, { min: 1.2, max: isGenkotsu ? 58 : 34, scale: 8.6 }); const push = clamp((impactSpeed - 120) * 0.62, 42, isGenkotsu ? 1040 : 260); const reason = isGenkotsu ? "\u3052\u3093\u3053\u3064" : (isStone ? "\u77f3\u306b\u6f70\u3055\u308c\u305f" : (impactSpeed >= PHYSICAL_DAMAGE_SPEED_THRESHOLD ? "\u9ad8\u901f\u3067\u843d\u3061\u3066\u304d\u305f\u9053\u5177\u306b\u885d\u7a81" : "\u843d\u3061\u3066\u304d\u305f\u9053\u5177\u306b\u5f53\u305f\u3063\u305f")); - if (isGenkotsu) { this.lastGenkotsuAt = this.time || 0; it.impactFlash = 1; it.lingerTimer = Math.max(it.lingerDuration || 2, 2); this.triggerGroundShake?.(2.0, 10); } + if (isGenkotsu) { worldRef.lastGenkotsuAt = worldRef.time || 0; it.impactFlash = 1; it.lingerTimer = Math.max(it.lingerDuration || 2, 2); worldRef.triggerGroundShake?.(2.0, 10); } let hit = 0; - for (const t of this.nearbyTarinai(it.x, it.y, radius + 54)) { - if (!t || t.dead || this.isTarinaiHiddenInNestBox(t)) continue; + for (const t of worldRef.nearbyTarinai(it.x, it.y, radius + 54)) { + if (!t || t.dead || worldRef.isTarinaiHiddenInNestBox(t)) continue; const d = Math.max(1, distXY(t.x, t.y, it.x, it.y)); const p = clamp(1 - d / (radius + t.radius), 0, 1); if (p <= 0) continue; let nx = (t.x - it.x) / d; let ny = (t.y - it.y) / d; if (!Number.isFinite(nx) || !Number.isFinite(ny) || Math.abs(nx) + Math.abs(ny) < 0.001) { - const a = deterministicAngle(this, "drop-impact-overlap-normal", it, t); + const a = deterministicAngle(worldRef, "drop-impact-overlap-normal", it, t); nx = Math.cos(a); ny = Math.sin(a); } @@ -52,9 +51,9 @@ hit += 1; } let ballHit = 0; - if (isGenkotsu && this.itemCounts?.ball) { + if (isGenkotsu && worldRef.itemCounts?.ball) { const ballRange = radius + Math.max(72, (it.r || 88) * 0.78); - const balls = this.nearbyItems?.(it.x, it.y, ballRange + 42, true) || this.items || []; + const balls = worldRef.nearbyItems?.(it.x, it.y, ballRange + 42, true) || worldRef.items || []; for (const ball of balls) { if (!ball || ball.dead || ball.type !== "ball") continue; const d = Math.max(1, distXY(ball.x, ball.y, it.x, it.y)); @@ -63,7 +62,7 @@ let nx = (ball.x - it.x) / d; let ny = (ball.y - it.y) / d; if (!Number.isFinite(nx) || !Number.isFinite(ny) || Math.abs(nx) + Math.abs(ny) < 0.001) { - const a = deterministicAngle(this, "genkotsu-ball-impact-normal", it, ball); + const a = deterministicAngle(worldRef, "genkotsu-ball-impact-normal", it, ball); nx = Math.cos(a); ny = Math.sin(a); } @@ -73,28 +72,27 @@ ball.vx = clamp((ball.vx || 0) + nx * ballPush, -1180, 1180); ball.vy = clamp((ball.vy || 0) + ny * ballPush - Math.min(180, ballPush * 0.12) * p, -1180, 1180); ball.spinVelocity = clamp((ball.spinVelocity || 0) + (nx >= 0 ? 1 : -1) * (10 + p * 12), -48, 48); - ball.lastPokedAt = this.time || 0; + ball.lastPokedAt = worldRef.time || 0; ballHit += 1; - this.effects?.push(new Effect("ring", ball.x, ball.y, { size: Math.max(18, (ball.r || 18) * 1.1), life: 0.20, color: "rgba(255,255,255,0.52)" })); + worldRef.effects?.push(new Effect("ring", ball.x, ball.y, { size: Math.max(18, (ball.r || 18) * 1.1), life: 0.20, color: "rgba(255,255,255,0.52)" })); } - if (ballHit > 0) this.markSpatialDirty?.("genkotsu-ball-impact"); + if (ballHit > 0) worldRef.markSpatialDirty?.("genkotsu-ball-impact"); } - if (damage > 0 && typeof damageNearbyStructures === "function") damageNearbyStructures(this, it.x, it.y, radius, damage, it); - const antHit = damage > 0 ? this.damageAntsInRadius(it.x, it.y, radius + 54, p => damage * (0.35 + p * 0.65), reason, { + const antHit = damage > 0 ? worldRef.damageAntsInRadius(it.x, it.y, radius + 54, p => damage * (0.35 + p * 0.65), reason, { push: p => push * (0.62 + p * 1.25), }) : 0; if (isGenkotsu) { - this.blastZunchiFrom?.(it.x, it.y, radius + 92, 2.75, { limit: 96, effectLimit: 6 }); - this.blastLoosePinsFrom?.(it.x, it.y, radius + 92, it, 2.75, "genkotsu-impact"); - const existingEffects = (this.effects || []).length; + worldRef.blastZunchiFrom?.(it.x, it.y, radius + 92, 2.75, { limit: 96, effectLimit: 6 }); + worldRef.blastLoosePinsFrom?.(it.x, it.y, radius + 92, it, 2.75, "genkotsu-impact"); + const existingEffects = (worldRef.effects || []).length; const ringCount = existingEffects > 180 ? 2 : 3; const burstCount = existingEffects > 180 ? 4 : 7; - for (let r = 0; r < ringCount; r++) this.effects.push(new Effect("ring", it.x, it.y, { size: 32 + r * 42, life: 0.32 + r * 0.07, color: r % 2 ? "rgba(255,203,54,0.68)" : "rgba(74,57,42,0.38)" })); - for (let i = 0; i < burstCount; i++) this.effects.push(new Effect("fight", it.x + deterministicRange(this, "genkotsu-impact-effect-x", -38, 38, it, i), it.y + deterministicRange(this, "genkotsu-impact-effect-y", -22, 28, it, i), { size: deterministicRange(this, "genkotsu-impact-effect-size", 12, 22, it, i), life: deterministicRange(this, "genkotsu-impact-effect-life", 0.22, 0.42, it, i), color: "rgba(255,205,46,0.78)" })); + for (let r = 0; r < ringCount; r++) worldRef.effects.push(new Effect("ring", it.x, it.y, { size: 32 + r * 42, life: 0.32 + r * 0.07, color: r % 2 ? "rgba(255,203,54,0.68)" : "rgba(74,57,42,0.38)" })); + for (let i = 0; i < burstCount; i++) worldRef.effects.push(new Effect("fight", it.x + deterministicRange(worldRef, "genkotsu-impact-effect-x", -38, 38, it, i), it.y + deterministicRange(worldRef, "genkotsu-impact-effect-y", -22, 28, it, i), { size: deterministicRange(worldRef, "genkotsu-impact-effect-size", 12, 22, it, i), life: deterministicRange(worldRef, "genkotsu-impact-effect-life", 0.22, 0.42, it, i), color: "rgba(255,205,46,0.78)" })); } else { - this.effects.push(new Effect("ring", it.x, it.y, { size: isStone ? 32 : 18, life: 0.34, color: isStone ? "rgba(128,96,58,0.76)" : "rgba(174,128,70,0.52)" })); + worldRef.effects.push(new Effect("ring", it.x, it.y, { size: isStone ? 32 : 18, life: 0.34, color: isStone ? "rgba(128,96,58,0.76)" : "rgba(174,128,70,0.52)" })); } - if (hit > 0 || antHit > 0 || isGenkotsu) this.spawnFallEffect(it.x, it.y + (it.r || 12) * 0.6, isGenkotsu ? 1.32 : (isStone ? 0.9 : 0.45)); + if (hit > 0 || antHit > 0 || isGenkotsu) worldRef.spawnFallEffect(it.x, it.y + (it.r || 12) * 0.6, isGenkotsu ? 1.32 : (isStone ? 0.9 : 0.45)); if (isGenkotsu) audio.genkotsuImpact?.(); else if (isStone) audio.stoneImpact?.(); else if (hit > 0 || antHit > 0) audio.poke?.(); @@ -104,15 +102,12 @@ const victims = []; if (hit > 0) victims.push(`${hit}\u5339\u306e\u305f\u308a\u306a\u3044`); if (antHit > 0) victims.push(`${antHit}\u5339\u306e\u30a2\u30ea`); - this.log(`${label}\u304c\u843d\u3061\u3001${victims.join("\u3068")}\u304c\u5dfb\u304d\u8fbc\u307e\u308c\u305f\u3002`, "accident"); + worldRef.log(`${label}\u304c\u843d\u3061\u3001${victims.join("\u3068")}\u304c\u5dfb\u304d\u8fbc\u307e\u308c\u305f\u3002`, "accident"); } - - }).call(worldRef, it); + } global.TarinaiImpactResponseSystem = Object.freeze({ - PHYSICAL_DAMAGE_SPEED_THRESHOLD, - physicalDamageFromImpactSpeed, itemDropImpact, }); })(typeof window !== "undefined" ? window : globalThis); diff --git a/js/input_mode_manager.js b/js/input_mode_manager.js index d96ae1f..c4f1423 100644 --- a/js/input_mode_manager.js +++ b/js/input_mode_manager.js @@ -6,7 +6,6 @@ class InputModeManager { constructor() { - this.storageKey = STORAGE_KEY; this.currentMode = this.loadMode(); this.classification = this.classify(); } @@ -71,6 +70,5 @@ } } - global.InputModeManager = InputModeManager; global.TarinaiInputMode = global.TarinaiInputMode || new InputModeManager(); })(typeof window !== "undefined" ? window : globalThis); diff --git a/js/item_dynamic_ball_system.js b/js/item_dynamic_ball_system.js index 7140312..17c5616 100644 --- a/js/item_dynamic_ball_system.js +++ b/js/item_dynamic_ball_system.js @@ -18,14 +18,14 @@ if (item.y < p) { item.y = p; item.vy = Math.abs(item.vy || 0) * 0.72; item.spinVelocity *= -0.72; } if (item.y > worldRef.h - p) { item.y = worldRef.h - p; item.vy = -Math.abs(item.vy || 0) * 0.72; item.spinVelocity *= -0.72; } resolveObstacleCollisions(item, dt, worldRef); - global.TarinaiItemDynamicPinSystem?.tryBallPickup?.(item, worldRef); + global.TarinaiItemDynamicPinSystem.tryBallPickup(item, worldRef); item.x = clamp(item.x, p, worldRef.w - p); item.y = clamp(item.y, p, worldRef.h - p); const groundFriction = Math.pow(0.72, dt); item.vx *= groundFriction; item.vy *= groundFriction; } - global.TarinaiItemDynamicPinSystem?.tryBallPickup?.(item, worldRef); + global.TarinaiItemDynamicPinSystem.tryBallPickup(item, worldRef); item.spin = (item.spin || 0) + (item.spinVelocity || 0) * dt + (item.vx || 0) * dt / Math.max(8, item.r || 18); item.spinVelocity *= Math.pow(0.65, dt); if (Math.hypot(item.vx || 0, item.vy || 0) < 0.18) { item.vx = 0; item.vy = 0; } @@ -42,8 +42,8 @@ // \u4e2d\u5fc3\u3067\u306f\u306a\u304f\u5916\u63a5\u534a\u5f84\u3067\u8ffd\u52a0\u8d70\u67fb\u3057\u3066\u3001\u9577\u3044\u68d2\u30fb\u5f80\u5fa9\u5e45\u306b\u3082\u885d\u7a81\u3055\u305b\u308b\u3002 if (Array.isArray(worldRef.items)) { for (const it of worldRef.items) { - if (!it || it.dead || it === item || !global.TarinaiMechanicalSystem?.isMechanicalType?.(it.type) || seen.has(it)) continue; - const reach = global.TarinaiMechanicalSystem?.reach?.(it) || worldRef.rotatorExtent?.(it) || Math.max(64, it.r || 64); + if (!it || it.dead || it === item || !global.TarinaiMechanicalSystem.isMechanicalType(it.type) || seen.has(it)) continue; + const reach = global.TarinaiMechanicalSystem.reach(it) || worldRef.rotatorExtent?.(it) || Math.max(64, it.r || 64); if (distXY(item.x, item.y, it.x, it.y) <= searchRadius + reach + 36) { items.push(it); seen.add(it); @@ -134,7 +134,7 @@ const restitution = Number.isFinite(rect.restitution) ? rect.restitution : 0.78; const velocityDamp = rect.bounce ? 1.0 : 0.94; const minBounceSpeed = Math.max(0, Number(rect.minBounceSpeed || 0) || 0); - if (rect.oriented && global.TarinaiPhysics?.bounceCircleOffRect) { + if (rect.oriented && global.TarinaiPhysics.bounceCircleOffRect) { const bounced = global.TarinaiPhysics.bounceCircleOffRect(item, rect, hitRadius, restitution, worldRef, { minBounceSpeed }); if (!bounced) return; item.vx *= velocityDamp; @@ -243,9 +243,5 @@ global.TarinaiItemDynamicBallSystem = Object.freeze({ update, resolveObstacleCollisions, - applyHayDrag, - resolveCircleBounce, - resolveRectBounce, - emitBounce, }); })(typeof window !== "undefined" ? window : globalThis); diff --git a/js/item_dynamic_duplicator_system.js b/js/item_dynamic_duplicator_system.js index 0f933da..ed61ffe 100644 --- a/js/item_dynamic_duplicator_system.js +++ b/js/item_dynamic_duplicator_system.js @@ -1,9 +1,7 @@ "use strict"; // Layer: entity-runtime/items/dynamic-system -// Owns duplicator item loading/reloading behavior. Direct stored-type helpers -// remain exposed from TarinaiDuplicatorRuntime for UI/tool commands and -// save/restore stability. +// Owns duplicator item loading/reloading behavior. (function (global) { function support() { return global.TarinaiItemLifecycleRuntimeSupport || {}; @@ -12,7 +10,7 @@ function update(item, dt, worldRef) { if (!item || item.dead || item.type !== "duplicator") return false; const s = support(); - const syncRoles = s.syncDuplicatorRoles || global.TarinaiDuplicatorRuntime?.syncRoles; + const syncRoles = s.syncDuplicatorRoles; const applyStoredPin = s.duplicatorApplyStoredPin; if (typeof syncRoles !== "function") return false; @@ -20,12 +18,6 @@ syncRoles(item); if (typeof applyStoredPin === "function") applyStoredPin(item, worldRef); item.duplicatorPinFlash = Math.max(0, (item.duplicatorPinFlash || 0) - dt); - // 39.15.34: auto-loading by contact is intentionally disabled. - // A duplicator accepts a stored item only through the explicit player - // click path (World.directSetDuplicatorAt / direct:true). This prevents - // dropped or pushed items from accidentally changing its setup. - item._duplicatorCandidateKey = ""; - item._duplicatorCandidateSince = 0; return true; } diff --git a/js/item_dynamic_pin_system.js b/js/item_dynamic_pin_system.js index c283e54..701b5e4 100644 --- a/js/item_dynamic_pin_system.js +++ b/js/item_dynamic_pin_system.js @@ -6,7 +6,7 @@ // behavior. Prototype methods delegate here. (function (global) { function isPin(item) { - return Boolean(global.TarinaiPinAttachmentSystem?.isPin?.(item) || (item && typeof global.isPinType === "function" && global.isPinType(item.type))); + return Boolean(global.TarinaiPinAttachmentSystem.isPin(item) || (item && typeof global.isPinType === "function" && global.isPinType(item.type))); } function ballById(worldRef, id) { @@ -28,7 +28,7 @@ detach(item, worldRef, "ball-lost"); return; } - if (global.TarinaiPinAttachmentSystem?.applyBallPose?.(item, ball, worldRef)) return; + if (global.TarinaiPinAttachmentSystem.applyBallPose(item, ball, worldRef)) return; const angle = (ball.spin || 0) + (item.pinBallOffsetAngle || 0); const distance = Math.max(4, Number(item.pinBallOffsetDistance || (ball.r || 18) * 0.55) || (ball.r || 18) * 0.55); item.prevX = item.x; @@ -134,9 +134,9 @@ updateBallLodged(item, dt, worldRef); return true; } - if (global.TarinaiPhysics?.applyKinematicItemMotion) { + if (global.TarinaiPhysics.applyKinematicItemMotion) { global.TarinaiPhysics.applyKinematicItemMotion(item, worldRef, dt, { padding: 26, bounce: 0.44, spinBounce: -0.68, frictionBase: 0.18, frictionRate: 0.85, spinFrictionBase: 0.38, spinRestDamp: 0.08, stopSpeed: 0.05, zeroBelow: 7.5 }); - global.TarinaiItemDynamicBallSystem?.resolveObstacleCollisions?.(item, dt, worldRef); + global.TarinaiItemDynamicBallSystem.resolveObstacleCollisions(item, dt, worldRef); } else { item.prevX = item.x; item.prevY = item.y; @@ -168,7 +168,7 @@ } function oshibyoAnchorFor(t, visualSide, item = { type: "oshibyo" }) { - return global.TarinaiPinAttachmentSystem?.tarinaiAnchor?.(item, t, { visualSide }) || { + return global.TarinaiPinAttachmentSystem.tarinaiAnchor(item, t, { visualSide }) || { x: t.x + visualSide * (t?.radius || 16) * 0.96, y: t.y + (t?.radius || 16) * 0.50, angle: visualSide * 0.48, @@ -181,7 +181,7 @@ function attach(item, t, worldRef, d = null) { if (!t || t.dead) return false; if (t.stuckPushpinId && t.stuckPushpinId !== item.id) return false; - const behavior = typeof pinBehaviorFor === "function" ? pinBehaviorFor(item.type) : null; + const behavior = pinBehaviorFor(item.type); const isOshibyo = Boolean(behavior?.blocksZunchi); const dx = item.x - t.x; const dy = item.y - t.y; @@ -190,7 +190,7 @@ item.deletable = false; item.pinTargetId = t.id; removeFromBall(item, worldRef); - const sharedAnchor = global.TarinaiPinAttachmentSystem?.tarinaiAnchor?.(item, t, { dx, dy, distance: distToTarget }); + const sharedAnchor = global.TarinaiPinAttachmentSystem.tarinaiAnchor(item, t, { dx, dy, distance: distToTarget }); const faceDir = t.facingDir ? t.facingDir() : (t.facing || 1); const faceSide = faceDir >= 0 ? 1 : -1; const visualSide = sharedAnchor?.visualSide ?? (isOshibyo ? -faceSide : faceSide); @@ -238,11 +238,11 @@ detach(item, worldRef, "owner-lost"); return; } - const behavior = typeof pinBehaviorFor === "function" ? pinBehaviorFor(item.type) : null; + const behavior = pinBehaviorFor(item.type); const isOshibyo = Boolean(behavior?.blocksZunchi); t.stuckPushpinId = item.id; item.deletable = false; - const sharedPose = global.TarinaiPinAttachmentSystem?.applyTarinaiPose?.(item, t, worldRef); + const sharedPose = global.TarinaiPinAttachmentSystem.applyTarinaiPose(item, t, worldRef); if (!sharedPose) { const faceDir = t.facingDir ? t.facingDir() : (t.facing || 1); const faceSide = faceDir >= 0 ? 1 : -1; @@ -290,7 +290,7 @@ function detach(item, worldRef, reason = "released") { const carriedBall = removeFromBall(item, worldRef); const t = worldRef?.liveTarinaiById?.(item.pinTargetId) || null; - const behavior = typeof pinBehaviorFor === "function" ? pinBehaviorFor(item.type) : null; + const behavior = pinBehaviorFor(item.type); const wasOshibyo = Boolean(behavior?.blocksZunchi); const storedZunchi = wasOshibyo && t ? Math.max(0, Math.floor(t.oshiriByoZunchiStock || 0)) : 0; if (t && t.stuckPushpinId === item.id) t.stuckPushpinId = null; @@ -352,14 +352,9 @@ global.TarinaiItemDynamicPinSystem = Object.freeze({ update, isPin, - tryStick, attach, - updateLodged, detach, - attachToBall, - transferFromBallToTarinai, transferBallPinsToTarinai, tryBallPickup, - updateBallLodged, }); })(typeof window !== "undefined" ? window : globalThis); diff --git a/js/item_dynamic_system.js b/js/item_dynamic_system.js index f76a044..7bebbe7 100644 --- a/js/item_dynamic_system.js +++ b/js/item_dynamic_system.js @@ -16,15 +16,15 @@ case "sprinkler": case "rope": case "rod": - return global.TarinaiItemDynamicToolSystem?.update(item, dt, worldRef) !== false; + return global.TarinaiItemDynamicToolSystem.update(item, dt, worldRef) !== false; case "ball": - return global.TarinaiItemDynamicBallSystem?.update(item, dt, worldRef) !== false; + return global.TarinaiItemDynamicBallSystem.update(item, dt, worldRef) !== false; case "duplicator": - return global.TarinaiItemDynamicDuplicatorSystem?.update(item, dt, worldRef) !== false; + return global.TarinaiItemDynamicDuplicatorSystem.update(item, dt, worldRef) !== false; case "zunchi": - return global.TarinaiItemDynamicZunchiSystem?.updateMotion(item, dt, worldRef) !== false; + return global.TarinaiItemDynamicZunchiSystem.updateMotion(item, dt, worldRef) !== false; default: - if (global.TarinaiItemDynamicPinSystem?.isPin(item)) { + if (global.TarinaiItemDynamicPinSystem.isPin(item)) { return global.TarinaiItemDynamicPinSystem.update(item, dt, worldRef) !== false; } return false; diff --git a/js/item_dynamic_tool_system.js b/js/item_dynamic_tool_system.js index 38f1658..ffc576d 100644 --- a/js/item_dynamic_tool_system.js +++ b/js/item_dynamic_tool_system.js @@ -5,9 +5,6 @@ // links, and static gate-fence ticking. Item.prototype methods delegate here for // direct Item.prototype calls; lifecycle dispatch also calls this boundary directly. (function (global) { - function support() { - return global.TarinaiItemLifecycleRuntimeSupport || {}; - } function updateFirecracker(item, dt, worldRef) { item.fuseTimer = Math.max(0, (item.fuseTimer ?? 5) - dt); @@ -28,29 +25,25 @@ } function updateRotator(item, dt, worldRef) { - return Boolean(global.TarinaiMechanicalSystem?.updateRotator?.(item, dt, worldRef)); + return Boolean(global.TarinaiMechanicalSystem.updateRotator(item, dt, worldRef)); } function updateReciprocator(item, dt, worldRef) { - return Boolean(global.TarinaiMechanicalSystem?.updateReciprocator?.(item, dt, worldRef)); + return Boolean(global.TarinaiMechanicalSystem.updateReciprocator(item, dt, worldRef)); } function updatePoisonBlock(item, dt, worldRef) { - return Boolean(global.TarinaiMechanicalSystem?.updatePoisonBlock?.(item, dt, worldRef)); - } - - function resolveMechanicalInteractions(item, dt, worldRef) { - return Boolean(global.TarinaiMechanicalSystem?.resolveInteractions?.(item, dt, worldRef)); + return Boolean(global.TarinaiMechanicalSystem.updatePoisonBlock(item, dt, worldRef)); } function updateFlexibleLink(item, dt, worldRef) { - return Boolean(global.TarinaiConstraintSystem?.updateFlexibleLink?.(item, dt, worldRef)); + return Boolean(global.TarinaiConstraintSystem.updateFlexibleLink(item, dt, worldRef)); } function updateRigidLink(item, dt, worldRef) { - return Boolean(global.TarinaiConstraintSystem?.updateRigidLink?.(item, dt, worldRef)); + return Boolean(global.TarinaiConstraintSystem.updateRigidLink(item, dt, worldRef)); } @@ -140,16 +133,6 @@ global.TarinaiItemDynamicToolSystem = Object.freeze({ update, - updateFirecracker, - updateGenkotsu, - updateRotator, - updateReciprocator, - updatePoisonBlock, - updateSprinkler, sprinklerRange, - updateFlexibleLink, - updateRigidLink, - resolveMechanicalInteractions, - updateGateFence, }); })(typeof window !== "undefined" ? window : globalThis); diff --git a/js/item_dynamic_zunchi_system.js b/js/item_dynamic_zunchi_system.js index add3c51..0348eab 100644 --- a/js/item_dynamic_zunchi_system.js +++ b/js/item_dynamic_zunchi_system.js @@ -9,10 +9,10 @@ const speed = Math.hypot(item.vx || 0, item.vy || 0); if (speed < 0.08) { item.vx = 0; item.vy = 0; - global.TarinaiItemDynamicBallSystem?.resolveObstacleCollisions?.(item, dt, worldRef); + global.TarinaiItemDynamicBallSystem.resolveObstacleCollisions(item, dt, worldRef); return true; } - if (global.TarinaiPhysics?.applyKinematicItemMotion) { + if (global.TarinaiPhysics.applyKinematicItemMotion) { global.TarinaiPhysics.applyKinematicItemMotion(item, worldRef, dt, { bounce: 0.34, frictionBase: 0.68, frictionRate: 2.4, spin: false, stopSpeed: 0.08 }); } else { item.prevX = item.x; @@ -29,7 +29,7 @@ item.vy *= slow; worldRef.drawListDirty = true; } - global.TarinaiItemDynamicBallSystem?.resolveObstacleCollisions?.(item, dt, worldRef); + global.TarinaiItemDynamicBallSystem.resolveObstacleCollisions(item, dt, worldRef); resolveHighSpeedTarinaiCollision(item, dt, worldRef, speed); item.spin = (item.spin || 0) + speed * dt / Math.max(8, item.r || 14); return true; @@ -78,8 +78,8 @@ const rainy = worldRef.weather === "light_rain"; const rainBoost = rainy ? 1.28 : 1; const groundId = worldRef?.groundType || "soil"; - const stageMul = Math.max(0.1, Number(global.TarinaiGround?.zunchiStageMultiplier?.(groundId) || 1) || 1); - const groundDecayMul = Math.max(0.1, Number(global.TarinaiGround?.zunchiDecayMultiplier?.(groundId) || 1) || 1); + const stageMul = Math.max(0.1, Number(global.TarinaiGround.zunchiStageMultiplier(groundId) || 1) || 1); + const groundDecayMul = Math.max(0.1, Number(global.TarinaiGround.zunchiDecayMultiplier(groundId) || 1) || 1); const decayBoost = (rainy ? 1.35 : 1) * groundDecayMul; let waterBoost = 0; const nearby = worldRef.nearbyItems ? worldRef.nearbyItems(item.x, item.y, 84) : (worldRef.items || []); @@ -108,5 +108,5 @@ return true; } - global.TarinaiItemDynamicZunchiSystem = Object.freeze({ updateMotion, resolveHighSpeedTarinaiCollision, updateLifecycle }); + global.TarinaiItemDynamicZunchiSystem = Object.freeze({ updateMotion, updateLifecycle }); })(typeof window !== "undefined" ? window : globalThis); diff --git a/js/item_effect_definitions.js b/js/item_effect_definitions.js index f2c6ac0..4320ab8 100644 --- a/js/item_effect_definitions.js +++ b/js/item_effect_definitions.js @@ -20,7 +20,7 @@ tags: ["item", "behavior", "power"], display: () => "\u6c38\u7d9a / \u4e0e\u30c0\u30e1\u30fc\u30b80.5\u500d / \u75c5\u6c17\u78ba\u73872\u500d", }, mercury: { - label: "\u6c34\u9280", color: "rgba(96, 146, 166, 0.82)", kind: "permanent", stack: "multiply", + label: "\u6c34\u9280", color: "rgba(96, 146, 166, 0.82)", kind: "permanent", tags: ["item", "life"], display: (_timer, t) => `\u6c38\u7d9a / \u5bff\u547dx${((t && t.mercuryLifeMultiplier) || 1.3).toFixed(2)}\uff08\u98df\u3079\u308b\u305f\u3073\u4e57\u7b97\uff09`, }, giant_drug: { @@ -258,25 +258,17 @@ detail: this.display(id, this.timerValue(tarinai, id), tarinai), })); } - labels() { return super.labels(); } - colors() { return super.colors(); } randomPool() { const itemEffects = [...this.defs.entries()] .filter(([id, def]) => Boolean(def.mysteryEligible && id !== "mystery_drug" && id !== "zunda_juice" && !(def.tags || []).includes("disease"))) .map(([id]) => id) .filter(id => id !== "mystery_drug" && id !== "zunda_juice"); - const diseases = global.TarinaiDiseaseRegistry?.mysteryPool?.() || []; + const diseases = global.TarinaiDiseaseRegistry.mysteryPool() || []; return [...new Set([...itemEffects, ...diseases])]; } - permanentIds() { return [...this.defs.entries()].filter(([, def]) => def.kind === "permanent").map(([id]) => id); } - timedIds() { return [...this.defs.entries()].filter(([, def]) => def.kind === "timed" || def.kind === "timer").map(([id]) => id); } - debugSnapshot() { - return [...this.defs.entries()].map(([id, def]) => ({ id, label: def.label, kind: def.kind, group: def.exclusiveGroup || "" })); - } } const registry = new EffectRegistry(rawDefinitions); global.TarinaiEffectRegistry = registry; - global.TarinaiEffectDefinitions = Object.freeze(rawDefinitions); })(typeof window !== "undefined" ? window : globalThis); diff --git a/js/item_food_definitions.js b/js/item_food_definitions.js index 1994181..f868588 100644 --- a/js/item_food_definitions.js +++ b/js/item_food_definitions.js @@ -4,23 +4,23 @@ (function (global) { const rawDefinitions = { - food: { label: "\u98df\u3079\u7269", nutrition: 18.0, hungerRelief: 1.0, interest: "normal", decayRateMultiplier: 1.0, hygienePenalty: 0.055 }, - sweet: { label: "\u305a\u3093\u3060\u9905", nutrition: 16.5, hungerRelief: 0.95, interest: "zunda_high", decayRateMultiplier: 0.82, hygienePenalty: 0.055, cures: ["disease_explosion", "disease_fight"] }, - love_mochi: { label: "\u3078\u3053\u9905", nutrition: 15.0, hungerRelief: 0.92, interest: "medicine_low", decayRateMultiplier: 1.0, hygienePenalty: 0.055, effectId: "love_mochi" }, - fight_mochi: { label: "\u3051\u3093\u304b\u9905", nutrition: 15.0, hungerRelief: 0.92, interest: "medicine_low", decayRateMultiplier: 1.0, hygienePenalty: 0.055, effectId: "fight_mochi" }, - sleep_drug: { label: "\u306d\u3080\u308a\u85ac", nutrition: 15.0, hungerRelief: 0.92, interest: "medicine_low", decayRateMultiplier: 1.0, hygienePenalty: 0.055, effectId: "sleep_drug" }, - laxative: { label: "\u4e0b\u5264", nutrition: 6.8, hungerRelief: 0, interest: "medicine_low", decayRateMultiplier: 1.0, hygienePenalty: 0.055, effectId: "laxative", passThrough: true }, - protein: { label: "\u30d7\u30ed\u30c6\u30a4\u30f3", nutrition: 15.0, hungerRelief: 0.92, interest: "medicine_low", decayRateMultiplier: 1.0, hygienePenalty: 0.055, effectId: "protein" }, - niteropu: { label: "\u30f3\u30a4\u30c6\u30ed\u30d7", nutrition: 15.0, hungerRelief: 0.92, interest: "medicine_low", decayRateMultiplier: 1.0, hygienePenalty: 0.055, effectId: "niteropu" }, - ammo: { label: "\u5f3e\u85ac", nutrition: 15.0, hungerRelief: 0.92, interest: "medicine_low", decayRateMultiplier: 1.0, hygienePenalty: 0.055, effectId: "ammo" }, - mystery_drug: { label: "\u602a\u3057\u3044\u85ac", nutrition: 15.0, hungerRelief: 0.92, interest: "medicine_low", decayRateMultiplier: 1.0, hygienePenalty: 0.055, effectId: "mystery_drug" }, - mercury: { label: "\u6c34\u9280", nutrition: 15.0, hungerRelief: 0.92, interest: "medicine_low", decayRateMultiplier: 1.0, hygienePenalty: 0.055, effectId: "mercury" }, - giant_drug: { label: "\u5de8\u5927\u85ac", nutrition: 15.0, hungerRelief: 0.92, interest: "medicine_low", decayRateMultiplier: 1.0, hygienePenalty: 0.055, effectId: "giant_drug" }, - dwarf_drug: { label: "\u77ee\u5c0f\u85ac", nutrition: 15.0, hungerRelief: 0.92, interest: "medicine_low", decayRateMultiplier: 1.0, hygienePenalty: 0.055, effectId: "dwarf_drug" }, - zunda_juice: { label: "\u305a\u3093\u3060\u6c41", nutrition: 15.0, hungerRelief: 0.92, interest: "medicine_low", decayRateMultiplier: 1.12, hygienePenalty: 0.055, effectId: "zunda_juice", removesItemEffects: true }, - grass: { label: "\u8349", nutrition: 2.6, hungerRelief: 0.55, interest: "grass_like", decayRateMultiplier: 1.0, hygienePenalty: 0 }, - water: { label: "\u6c34\u6ef4", nutrition: 0, hungerRelief: 0, interest: "water", decayRateMultiplier: 1.0, hygienePenalty: 0 }, - ant_corpse: { label: "\u30a2\u30ea\u306e\u6b7b\u9ab8", nutrition: 4.2, hungerRelief: 0.62, interest: "corpse", decayRateMultiplier: 1.0, hygienePenalty: 0.065 } + food: { label: "\u98df\u3079\u7269", nutrition: 18.0, hungerRelief: 1.0, interest: "normal", decayRateMultiplier: 1.0 }, + sweet: { label: "\u305a\u3093\u3060\u9905", nutrition: 16.5, hungerRelief: 0.95, interest: "zunda_high", decayRateMultiplier: 0.82 }, + love_mochi: { label: "\u3078\u3053\u9905", nutrition: 15.0, hungerRelief: 0.92, interest: "medicine_low", decayRateMultiplier: 1.0, effectId: "love_mochi" }, + fight_mochi: { label: "\u3051\u3093\u304b\u9905", nutrition: 15.0, hungerRelief: 0.92, interest: "medicine_low", decayRateMultiplier: 1.0, effectId: "fight_mochi" }, + sleep_drug: { label: "\u306d\u3080\u308a\u85ac", nutrition: 15.0, hungerRelief: 0.92, interest: "medicine_low", decayRateMultiplier: 1.0, effectId: "sleep_drug" }, + laxative: { label: "\u4e0b\u5264", nutrition: 6.8, hungerRelief: 0, interest: "medicine_low", decayRateMultiplier: 1.0, effectId: "laxative" }, + protein: { label: "\u30d7\u30ed\u30c6\u30a4\u30f3", nutrition: 15.0, hungerRelief: 0.92, interest: "medicine_low", decayRateMultiplier: 1.0, effectId: "protein" }, + niteropu: { label: "\u30f3\u30a4\u30c6\u30ed\u30d7", nutrition: 15.0, hungerRelief: 0.92, interest: "medicine_low", decayRateMultiplier: 1.0, effectId: "niteropu" }, + ammo: { label: "\u5f3e\u85ac", nutrition: 15.0, hungerRelief: 0.92, interest: "medicine_low", decayRateMultiplier: 1.0, effectId: "ammo" }, + mystery_drug: { label: "\u602a\u3057\u3044\u85ac", nutrition: 15.0, hungerRelief: 0.92, interest: "medicine_low", decayRateMultiplier: 1.0, effectId: "mystery_drug" }, + mercury: { label: "\u6c34\u9280", nutrition: 15.0, hungerRelief: 0.92, interest: "medicine_low", decayRateMultiplier: 1.0, effectId: "mercury" }, + giant_drug: { label: "\u5de8\u5927\u85ac", nutrition: 15.0, hungerRelief: 0.92, interest: "medicine_low", decayRateMultiplier: 1.0, effectId: "giant_drug" }, + dwarf_drug: { label: "\u77ee\u5c0f\u85ac", nutrition: 15.0, hungerRelief: 0.92, interest: "medicine_low", decayRateMultiplier: 1.0, effectId: "dwarf_drug" }, + zunda_juice: { label: "\u305a\u3093\u3060\u6c41", nutrition: 15.0, hungerRelief: 0.92, interest: "medicine_low", decayRateMultiplier: 1.12, effectId: "zunda_juice", removesItemEffects: true }, + grass: { label: "\u8349", nutrition: 2.6, hungerRelief: 0.55, interest: "grass_like", decayRateMultiplier: 1.0 }, + water: { label: "\u6c34\u6ef4", nutrition: 0, hungerRelief: 0, interest: "water", decayRateMultiplier: 1.0 }, + ant_corpse: { label: "\u30a2\u30ea\u306e\u6b7b\u9ab8", nutrition: 4.2, hungerRelief: 0.62, interest: "corpse", decayRateMultiplier: 1.0 } }; class FoodRegistry extends global.TarinaiRegistryBase { @@ -29,12 +29,6 @@ .filter(([, def]) => def.interest && !["grass_like", "water", "corpse"].includes(def.interest)) .map(([id]) => id); } - typesByInterest(...interests) { - const wanted = new Set(interests.flat().filter(Boolean)); - return [...this.defs.entries()] - .filter(([, def]) => wanted.has(def.interest)) - .map(([id]) => id); - } paramEffectTypes() { return [...this.defs.entries()].filter(([, def]) => def.effectId && !def.effectId.endsWith("_mochi") && def.effectId !== "sleep_drug").map(([id]) => id); } @@ -46,14 +40,10 @@ const value = this.get(type)?.hungerRelief; return Number.isFinite(value) ? value : fallback; } - effectId(type = "") { return this.get(type)?.effectId || ""; } passiveDecayMultiplier(type = "") { return this.get(type)?.decayRateMultiplier ?? 1; } - hygienePenalty(type = "") { return this.get(type)?.hygienePenalty ?? 0.055; } - debugSnapshot() { return super.debugSnapshot((id, def) => ({ id, label: def.label, interest: def.interest, effectId: def.effectId || "" })); } } const registry = new FoodRegistry(rawDefinitions); global.TarinaiFoodRegistry = registry; - global.TarinaiFoodDefinitions = Object.freeze(rawDefinitions); })(typeof window !== "undefined" ? window : globalThis); diff --git a/js/item_lifecycle_decay_system.js b/js/item_lifecycle_decay_system.js index de36516..8f7f4a5 100644 --- a/js/item_lifecycle_decay_system.js +++ b/js/item_lifecycle_decay_system.js @@ -1,8 +1,8 @@ "use strict"; // Layer: entity-runtime/items/decay-system -// Owns passive amount decay and spoilage side effects for water, food, -// transient traces, splats, and ant corpses. The lifecycle step is now only a +// Owns passive amount decay for water, food, transient traces, splats, +// and ant corpses. The lifecycle step is now only a // thin pipeline adapter. (function (global) { const MEDICINE_LIKE_FOOD_TYPES = Object.freeze([ @@ -11,7 +11,7 @@ ]); function decayServingFood(item, dt, worldRef) { - if (typeof isServingFoodType !== "function" || !isServingFoodType(item.type)) return false; + if (!isServingFoodType(item.type)) return false; const interval = passiveFoodDecayInterval(worldRef); item.passiveFoodDecayTimer = (item.passiveFoodDecayTimer || 0) + dt; let changed = false; @@ -26,8 +26,6 @@ changed = true; item.foodServingsRemaining = Math.max(0, before - lost); item.amount = item.foodServingsRemaining; - const penalty = global.TarinaiItemRegistry?.food?.hygienePenalty?.(item.type) ?? PASSIVE_FOOD_HYGIENE_PENALTY_PER_SERVING; - worldRef?.registerFoodSpoilage?.(lost * penalty, item); 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; @@ -38,8 +36,6 @@ item.amount -= lost; if (lost > 0) { changed = true; - const penalty = global.TarinaiItemRegistry?.food?.hygienePenalty?.(item.type) ?? 0.002; - worldRef?.registerFoodSpoilage?.(lost * penalty, item); worldRef?.markTerrainDirty?.("food-passive-decay"); worldRef?.emit?.("item:decayed", { item, type: item.type, amount: lost, passive: true }); } @@ -58,9 +54,5 @@ return { done: false }; } - global.TarinaiItemDecaySystem = Object.freeze({ - MEDICINE_LIKE_FOOD_TYPES, - decayServingFood, - update, - }); + global.TarinaiItemDecaySystem = Object.freeze({ update }); })(typeof window !== "undefined" ? window : globalThis); diff --git a/js/item_lifecycle_growth_system.js b/js/item_lifecycle_growth_system.js index fbf63fb..d29aaeb 100644 --- a/js/item_lifecycle_growth_system.js +++ b/js/item_lifecycle_growth_system.js @@ -1,17 +1,9 @@ "use strict"; // Layer: entity-runtime/items/growth-system -// Owns passive grass normalization and zunchi lifecycle stage ticking. The -// lifecycle step is kept as a pipeline adapter only. +// Owns zunchi lifecycle stage ticking. Grass growth is handled by +// simulation_item_ant_system.js via sparse random stage advances. (function (global) { - function updateGrass(item) { - if (!item || item.dead || item.type !== "grass") return false; - // Grass is intentionally not simulated per item. World.update randomly - // advances one grass by one discrete stage every few seconds. - normalizeGrassStage(item); - return true; - } - function updateZunchiLifecycle(item, dt, worldRef) { if (!item || item.dead || item.type !== "zunchi") return false; item.lifecycleTimer += dt; @@ -19,20 +11,15 @@ if (item.lifecycleTimer < interval) return false; const lifecycleDt = Math.min(item.lifecycleTimer, 2.4); item.lifecycleTimer = 0; - global.TarinaiItemDynamicZunchiSystem?.updateLifecycle(item, lifecycleDt, worldRef); + global.TarinaiItemDynamicZunchiSystem.updateLifecycle(item, lifecycleDt, worldRef); return true; } function update(item, dt, worldRef) { if (!item || item.dead) return { done: true }; - if (item.type === "grass") updateGrass(item); - else if (item.type === "zunchi") updateZunchiLifecycle(item, dt, worldRef); + if (item.type === "zunchi") updateZunchiLifecycle(item, dt, worldRef); return { done: false }; } - global.TarinaiItemGrowthSystem = Object.freeze({ - updateGrass, - updateZunchiLifecycle, - update, - }); + global.TarinaiItemGrowthSystem = Object.freeze({ update }); })(typeof window !== "undefined" ? window : globalThis); diff --git a/js/item_lifecycle_legacy_system.js b/js/item_lifecycle_legacy_system.js deleted file mode 100644 index 2cd3056..0000000 --- a/js/item_lifecycle_legacy_system.js +++ /dev/null @@ -1,13 +0,0 @@ -"use strict"; - -// Layer: entity-runtime/items/legacy-lifecycle -// Compatibility shim. The former monolithic fallback was removed so item -// behavior has a single source of truth: TarinaiItemLifecyclePipeline. -(function (global) { - function updateOne(item, dt, worldRef = global.world) { - if (!item || item.dead || !global.TarinaiItemLifecyclePipeline?.updateOne) return false; - return global.TarinaiItemLifecyclePipeline.updateOne(item, dt, worldRef) !== false; - } - - global.TarinaiItemLifecycleLegacySystem = Object.freeze({ updateOne }); -})(typeof window !== "undefined" ? window : globalThis); diff --git a/js/item_lifecycle_pipeline.js b/js/item_lifecycle_pipeline.js index d2c1809..eb6db1f 100644 --- a/js/item_lifecycle_pipeline.js +++ b/js/item_lifecycle_pipeline.js @@ -11,27 +11,24 @@ "TarinaiItemGrowthLifecycleStep", ]); + let cachedSteps = null; function concreteSteps() { - return CONCRETE_STEP_NAMES.map(name => global[name]).filter(step => step && typeof step.update === "function"); + if (!cachedSteps) cachedSteps = CONCRETE_STEP_NAMES.map(name => global[name]); + return cachedSteps; } - function canRunConcretePipeline() { - return concreteSteps().length === CONCRETE_STEP_NAMES.length; - } function updateOne(item, dt, worldRef, options = {}) { - if (!item || item.dead || !canRunConcretePipeline()) return false; + if (!item || item.dead) return false; + const steps = concreteSteps(); + if (steps.length !== CONCRETE_STEP_NAMES.length) return false; const context = options.context || {}; - for (const step of concreteSteps()) { + for (const step of steps) { const result = step.update(item, dt, worldRef, context) || {}; if (result.done) return result.ok !== false; } return true; } - global.TarinaiItemLifecyclePipeline = Object.freeze({ - stepNames: CONCRETE_STEP_NAMES, - canRunConcretePipeline, - updateOne, - }); + global.TarinaiItemLifecyclePipeline = Object.freeze({ updateOne }); })(typeof window !== "undefined" ? window : globalThis); diff --git a/js/item_lifecycle_runtime.js b/js/item_lifecycle_runtime.js deleted file mode 100644 index 629e87e..0000000 --- a/js/item_lifecycle_runtime.js +++ /dev/null @@ -1,42 +0,0 @@ -"use strict"; - -// Layer: entity-runtime/items/prototype-boundary -// Keeps Item.prototype methods available while implementation ownership -// lives in lifecycle/dynamic systems. - -Object.assign(Item.prototype, { -update(dt, worldRef = world) { return window.TarinaiItemRuntime?.updateOne?.(this, dt, worldRef) ?? window.TarinaiItemLifecyclePipeline?.updateOne?.(this, dt, worldRef) ?? false; }, - - -updateRotator(dt, worldRef) { return window.TarinaiItemDynamicToolSystem?.updateRotator?.(this, dt, worldRef); }, - -updateDuplicator(dt, worldRef) { return window.TarinaiItemDynamicDuplicatorSystem?.update?.(this, dt, worldRef); }, - -updateZunchiMotion(dt, worldRef) { return window.TarinaiItemDynamicZunchiSystem?.updateMotion?.(this, dt, worldRef); }, - -resolveHighSpeedZunchiTarinaiCollision(dt, worldRef, speed = 0) { return window.TarinaiItemDynamicZunchiSystem?.resolveHighSpeedTarinaiCollision?.(this, dt, worldRef, speed); }, - -updateBall(dt, worldRef) { return window.TarinaiItemDynamicBallSystem?.update?.(this, dt, worldRef); }, - -resolveBallObstacleCollisions(dt, worldRef) { return window.TarinaiItemDynamicBallSystem?.resolveObstacleCollisions?.(this, dt, worldRef); }, - -applyBallHayDrag(bed, dt, worldRef) { return window.TarinaiItemDynamicBallSystem?.applyHayDrag?.(this, bed, dt, worldRef); }, - -resolveBallCircleBounce(obstacle, hitRadius, restitution, worldRef) { return window.TarinaiItemDynamicBallSystem?.resolveCircleBounce?.(this, obstacle, hitRadius, restitution, worldRef); }, - -resolveBallRectBounce(rect, worldRef, source = null) { return window.TarinaiItemDynamicBallSystem?.resolveRectBounce?.(this, rect, worldRef, source); }, - -emitBallBounce(worldRef, obstacle) { return window.TarinaiItemDynamicBallSystem?.emitBounce?.(this, worldRef, obstacle); }, - -updatePushpin(dt, worldRef) { return window.TarinaiItemDynamicPinSystem?.update?.(this, dt, worldRef); }, - -tryStickPushpin(worldRef) { return window.TarinaiItemDynamicPinSystem?.tryStick?.(this, worldRef); }, - -attachPushpin(t, worldRef, d = null) { return window.TarinaiItemDynamicPinSystem?.attach?.(this, t, worldRef, d); }, - -updateLodgedPushpin(dt, worldRef) { return window.TarinaiItemDynamicPinSystem?.updateLodged?.(this, dt, worldRef); }, - -detachPushpin(worldRef, reason = "released") { return window.TarinaiItemDynamicPinSystem?.detach?.(this, worldRef, reason); }, - -updateZunchi(dt, worldRef) { return window.TarinaiItemDynamicZunchiSystem?.updateLifecycle?.(this, dt, worldRef); } -}); diff --git a/js/item_lifecycle_step_decay.js b/js/item_lifecycle_step_decay.js index 3ed2f47..79fdd99 100644 --- a/js/item_lifecycle_step_decay.js +++ b/js/item_lifecycle_step_decay.js @@ -6,7 +6,7 @@ (function (global) { function update(item, dt, worldRef) { if (!item || item.dead) return { done: true }; - return global.TarinaiItemDecaySystem?.update(item, dt, worldRef) || { done: false }; + return global.TarinaiItemDecaySystem.update(item, dt, worldRef) || { done: false }; } global.TarinaiItemDecayLifecycleStep = Object.freeze({ update }); diff --git a/js/item_lifecycle_step_dynamic.js b/js/item_lifecycle_step_dynamic.js index 3d9cac5..0781caf 100644 --- a/js/item_lifecycle_step_dynamic.js +++ b/js/item_lifecycle_step_dynamic.js @@ -6,7 +6,7 @@ (function (global) { function update(item, dt, worldRef) { if (!item || item.dead) return { done: true }; - global.TarinaiItemDynamicSystem?.update(item, dt, worldRef); + global.TarinaiItemDynamicSystem.update(item, dt, worldRef); return { done: false }; } diff --git a/js/item_lifecycle_step_growth.js b/js/item_lifecycle_step_growth.js index b9349f8..fc9a3f4 100644 --- a/js/item_lifecycle_step_growth.js +++ b/js/item_lifecycle_step_growth.js @@ -6,7 +6,7 @@ (function (global) { function update(item, dt, worldRef) { if (!item || item.dead) return { done: true }; - return global.TarinaiItemGrowthSystem?.update(item, dt, worldRef) || { done: false }; + return global.TarinaiItemGrowthSystem.update(item, dt, worldRef) || { done: false }; } global.TarinaiItemGrowthLifecycleStep = Object.freeze({ update }); diff --git a/js/item_lifecycle_step_legacy.js b/js/item_lifecycle_step_legacy.js deleted file mode 100644 index b70383a..0000000 --- a/js/item_lifecycle_step_legacy.js +++ /dev/null @@ -1,13 +0,0 @@ -"use strict"; - -// Layer: entity-runtime/items/lifecycle-step -// Compatibility shim for old step lists. Do not call a captured monolithic -// update body; delegate to the concrete pipeline only. -(function (global) { - function update(item, dt, worldRef) { - const ok = global.TarinaiItemLifecyclePipeline?.updateOne?.(item, dt, worldRef); - return { done: true, ok: ok !== false }; - } - - global.TarinaiItemLegacyLifecycleStep = Object.freeze({ update }); -})(typeof window !== "undefined" ? window : globalThis); diff --git a/js/item_lifecycle_support.js b/js/item_lifecycle_support.js index 4baac7c..dbb97fc 100644 --- a/js/item_lifecycle_support.js +++ b/js/item_lifecycle_support.js @@ -15,13 +15,13 @@ function duplicatorLoadTypeForItem(item) { if (!item || item.dead || item.type === "duplicator") return ""; const type = String(item.type || ""); if (!type || type === "grass") return ""; - if ((typeof isPinType === "function" && isPinType(type)) && item.pinState === "lodged") return ""; + if ((isPinType(type)) && item.pinState === "lodged") return ""; // Explicit list first. \u3053\u308c\u3067\u300c\u305a\u3093\u3060\u9905\u300d\u300c\u3078\u3053\u9905\u300d\u300c\u3051\u3093\u304b\u9905\u300d\u304c // serving-food \u5224\u5b9a\u3084 medicine role \u306e\u63fa\u308c\u306b\u5de6\u53f3\u3055\u308c\u305a\u8907\u88fd\u6a5f\u3078\u5165\u308b\u3002 if (DUPLICATOR_STORABLE_TYPES.has(type)) return type; - if (typeof isServingFoodType === "function" && isServingFoodType(type)) return type; - if (typeof isParamEffectItemType === "function" && isParamEffectItemType(type)) return type; - const foodDef = global.TarinaiItemRegistry?.food?.get?.(type); + if (isServingFoodType(type)) return type; + if (isParamEffectItemType(type)) return type; + const foodDef = global.TarinaiItemRegistry.food?.get?.(type); if (foodDef && (Number(foodDef.nutrition || 0) > 0 || foodDef.effectId)) return type; if (typeof roleMatches === "function" && roleMatches(item, "medicine")) return type; return ""; @@ -29,37 +29,17 @@ function duplicatorLoadTypeForItem(item) { -function itemAngleForRuntime(item) { - return typeof itemAngleFor === "function" ? itemAngleFor(item) : (Number(item?.angle) || 0); -} - -const DUPLICATOR_LOAD_DWELL_SECONDS = 0.38; -const DUPLICATOR_REPLACE_DWELL_SECONDS = 0.70; - function duplicatorLoadPoint(duplicator) { const r = duplicator?.r || 34; return { x: (duplicator?.x || 0) + r * 0.62, y: (duplicator?.y || 0) + r * 0.24 }; } -function duplicatorCandidateReach(item) { - return Math.max(20, Math.min(36, (item?.r || 12) * 0.82 + 13)); -} - -function duplicatorCanLoadNow(duplicator, worldRef) { - if (!duplicator || duplicator.type !== "duplicator") return false; - if (duplicator.playerHeld || duplicator._heldByPlayer) return false; - const now = Number(worldRef?.time || 0); - if (now < Number(duplicator.duplicatorLoadSuppressedUntil || 0)) return false; - if (Math.hypot(duplicator.vx || 0, duplicator.vy || 0) > 24) return false; - return true; -} - function syncDuplicatorRoles(item) { if (!item || item.type !== "duplicator") return; const type = String(item.storedFoodType || ""); - const pinLoaded = Boolean(type && typeof isPinType === "function" && isPinType(type)); + const pinLoaded = Boolean(type && isPinType(type)); item.roles.food = Boolean(type && !pinLoaded); - item.roles.medicine = Boolean(type && !pinLoaded && (type === "sweet" || type === "water" || type === "zunda_juice" || type === "sleep_drug" || (typeof isParamEffectItemType === "function" && isParamEffectItemType(type)))); + item.roles.medicine = Boolean(type && !pinLoaded && (type === "sweet" || type === "water" || type === "zunda_juice" || type === "sleep_drug" || (isParamEffectItemType(type)))); item.roles.drink = Boolean(!pinLoaded && (type === "water" || type === "zunda_juice")); item.roles.danger = pinLoaded; } @@ -74,15 +54,12 @@ function duplicatorSetStoredType(duplicator, storedType = "", worldRef = null, o if (!loadType) return false; const changed = duplicator.storedFoodType !== loadType; if (!changed && duplicator.storedFoodType && !options.force) return true; - const def = typeof itemDefinition === "function" ? itemDefinition(loadType) : null; - const newLabel = def?.label || (typeof toolLabel === "function" ? toolLabel(loadType) : loadType); + const def = itemDefinition(loadType); + const newLabel = def?.label || toolLabel(loadType); duplicator.storedFoodType = loadType; duplicator.storedFoodLabel = newLabel; syncDuplicatorRoles(duplicator); - duplicator.loadedAt = Number(worldRef?.time || 0); - duplicator._duplicatorCandidateKey = ""; - duplicator._duplicatorCandidateSince = 0; - duplicator.duplicatorPinFlash = typeof isPinType === "function" && isPinType(loadType) ? 0.28 : (duplicator.duplicatorPinFlash || 0); + duplicator.duplicatorPinFlash = isPinType(loadType) ? 0.28 : (duplicator.duplicatorPinFlash || 0); const consume = options.consumeItem; if (consume && typeof consume === "object") { consume.amount = 0; @@ -92,20 +69,13 @@ function duplicatorSetStoredType(duplicator, storedType = "", worldRef = null, o worldRef?.markSpatialDirty?.(changed ? "duplicator-direct-reloaded" : "duplicator-direct-loaded"); worldRef?.markTerrainDirty?.(changed ? "duplicator-direct-reloaded" : "duplicator-direct-loaded"); if (options.emit !== false) worldRef?.emit?.("duplicator:loaded", { duplicator, foodType: loadType, replaced: changed, direct: Boolean(options.direct) }); - if (options.log !== false) worldRef?.log?.(`\u8907\u88fd\u6a5f\u306b${duplicator.storedFoodLabel}\u3092\u30bb\u30c3\u30c8\u3057\u305f\u3002`, typeof isPinType === "function" && isPinType(loadType) ? "observe" : "food"); + if (options.log !== false) worldRef?.log?.(`\u8907\u88fd\u6a5f\u306b${duplicator.storedFoodLabel}\u3092\u30bb\u30c3\u30c8\u3057\u305f\u3002`, isPinType(loadType) ? "observe" : "food"); return true; } -global.TarinaiDuplicatorRuntime = Object.freeze({ - loadTypeForItem: duplicatorLoadTypeForItem, - loadPoint: duplicatorLoadPoint, - setStoredType: duplicatorSetStoredType, - syncRoles: syncDuplicatorRoles, - }); - function duplicatorApplyStoredPin(duplicator, worldRef) { const storedType = String(duplicator?.storedFoodType || ""); - if (!duplicator || duplicator.type !== "duplicator" || !(typeof isPinType === "function" && isPinType(storedType))) return false; + if (!duplicator || duplicator.type !== "duplicator" || !isPinType(storedType)) return false; const now = Number(worldRef?.time || 0); if (now < Number(duplicator.nextDuplicatedPinAt || 0)) return false; const dish = duplicatorLoadPoint(duplicator); @@ -121,7 +91,6 @@ function duplicatorApplyStoredPin(duplicator, worldRef) { pin.noStickUntil = 0; pin.noStickTargetId = ""; pin.noStickTargetUntil = 0; - pin.duplicatedFromId = duplicator.id || ""; const added = worldRef?.addItem?.(pin, "duplicator-pin", { terrain: false }) || null; if (!added && worldRef?.items) { pin.world = worldRef; @@ -129,7 +98,7 @@ function duplicatorApplyStoredPin(duplicator, worldRef) { worldRef.markItemBucketsDirty?.("duplicator-pin"); worldRef.markSpatialDirty?.("duplicator-pin"); } - const ok = pin.attachPushpin?.(t, worldRef, distXY(dish.x, dish.y, t.x, t.y)); + const ok = global.TarinaiItemDynamicPinSystem.attach(pin, t, worldRef, distXY(dish.x, dish.y, t.x, t.y)); if (!ok) { pin.amount = 0; continue; @@ -146,16 +115,11 @@ function duplicatorApplyStoredPin(duplicator, worldRef) { } global.TarinaiItemLifecycleRuntimeSupport = Object.freeze({ - itemAngleForRuntime, duplicatorLoadTypeForItem, duplicatorLoadPoint, - duplicatorCandidateReach, - duplicatorCanLoadNow, syncDuplicatorRoles, duplicatorSetStoredType, duplicatorApplyStoredPin, - DUPLICATOR_LOAD_DWELL_SECONDS, - DUPLICATOR_REPLACE_DWELL_SECONDS, }); diff --git a/js/item_registry.js b/js/item_registry.js index d3d428a..f2edf4e 100644 --- a/js/item_registry.js +++ b/js/item_registry.js @@ -1,74 +1,9 @@ "use strict"; -// Public facade joining the split item registry sources. +// Public facade for registries that are still consumed across modules. (function (global) { - function itemTypeDefinition(type = "") { - const id = String(type || ""); - const tool = typeof global.itemDefinition === "function" ? global.itemDefinition(id) : null; - const foodDefinitions = global.TarinaiFoodDefinitions || {}; - const effectDefinitions = global.TarinaiEffectDefinitions || {}; - const visualDefinitions = global.TarinaiItemVisualDefinitions || {}; - const traits = global.ITEM_TRAITS || global.TarinaiToolRegistry?.traits || {}; - const food = foodDefinitions[id] || null; - const effectId = food?.effectId || (effectDefinitions[id] ? id : ""); - const effect = effectId ? (effectDefinitions[effectId] || null) : null; - const visual = visualDefinitions[id] || null; - return { - id, - tool, - food, - effect, - effectId, - visual, - label: tool?.label || food?.label || effect?.label || id, - radius: tool?.radius, - amount: tool?.amount, - placeable: Boolean(tool?.placeable), - scalable: Boolean(tool?.scalable), - traits: Object.fromEntries(Object.keys(traits).map(trait => [trait, typeof global.itemHasTrait === "function" ? global.itemHasTrait(id, trait) : false])), - }; - } - - function itemTypeLabel(type = "") { - return itemTypeDefinition(type).label; - } - - function itemTypeDebugSnapshot() { - const toolDefinitions = global.TOOL_DEFINITIONS || global.TarinaiToolRegistry?.definitions || {}; - const foodDefinitions = global.TarinaiFoodDefinitions || {}; - const effectDefinitions = global.TarinaiEffectDefinitions || {}; - const visualDefinitions = global.TarinaiItemVisualDefinitions || {}; - const ids = new Set([ - ...Object.values(toolDefinitions).map(def => def.itemType || def.id), - ...Object.keys(foodDefinitions), - ...Object.keys(effectDefinitions), - ...Object.keys(visualDefinitions), - ]); - return [...ids].sort().map(id => itemTypeDefinition(id)); - } - - global.itemTypeDefinition = itemTypeDefinition; - global.itemTypeLabel = itemTypeLabel; - global.itemTypeDebugSnapshot = itemTypeDebugSnapshot; - global.TarinaiItemRegistry = Object.freeze({ - definition: itemTypeDefinition, - label: itemTypeLabel, - debugSnapshot: itemTypeDebugSnapshot, - tool: global.toolDefinition, - item: global.itemDefinition, food: global.TarinaiFoodRegistry, effect: global.TarinaiEffectRegistry, - visual: global.itemVisualDefinition, - hasTrait: global.itemHasTrait, - radius: global.itemRadiusFor, - amount: global.itemAmountFor, - categories: global.toolCategories, - physicsToolIds: () => [...(global.PHYSICS_TOOL_IDS || [])], - isFence: global.isFenceItemType, - isMechanical: global.isMechanicalItemType, - isLink: global.isLinkItemType, - scalableToolIds: global.scalableToolIds, - pinBehavior: global.pinBehaviorFor, }); })(typeof window !== "undefined" ? window : globalThis); diff --git a/js/item_render_runtime.js b/js/item_render_runtime.js index 173723d..95153e5 100644 --- a/js/item_render_runtime.js +++ b/js/item_render_runtime.js @@ -3,7 +3,7 @@ // Item field rendering. Kept separate from construction and lifecycle logic. function segmentPathCacheKey(item, segments, extra = "") { - const version = Number(item?._mechanicalShapeVersion || item?._segmentsVersion || 0) || 0; + const version = Number(item?._mechanicalShapeVersion || 0) || 0; const len = Array.isArray(segments) ? segments.length : 0; let h = len * 2654435761; if (Array.isArray(segments)) { @@ -52,7 +52,7 @@ function strokeSegments(ctx, item, segments, extra = "") { } function mechanicalRenderTier() { - return window.TarinaiPerf?.renderQualityTier?.() || "high"; + return window.TarinaiPerf.renderQualityTier(); } function drawRopeParticlePath(ctx, particles, ox, oy) { @@ -88,7 +88,7 @@ draw(ctx, t, lighting = null) { const shadow = projectedShadowParams(lightState, Math.max(0.4, this.r / 18)); if (this.type !== "trace" && this.type !== "splat") { const zunchiId = this.type === "zunchi" ? (this.zunchiVariant || "zunchi") : null; - const zunchiImg = zunchiId ? (typeof getRenderableImage === "function" ? getRenderableImage(zunchiId, "zunchi") : (images.get(zunchiId) || images.get("zunchi"))) : null; + const zunchiImg = zunchiId ? getRenderableImage(zunchiId, "zunchi") : null; if (zunchiImg) { const metrics = getImageMetrics(zunchiId || "zunchi") || getImageMetrics("zunchi"); const ratio = metrics?.ratio || 178 / 236; @@ -127,7 +127,7 @@ draw(ctx, t, lighting = null) { if (isConsumableSpriteType(this.type)) { drawConsumableFieldSprite(ctx, this.type, this.r, this.seed); } else if (this.type === "genkotsu") { - const img = typeof getRenderableImage === "function" ? getRenderableImage("genkotsu", "genkotsu") : images.get("genkotsu"); + const img = getRenderableImage("genkotsu", "genkotsu"); const impact = clamp(this.impactFlash || 0, 0, 1); ctx.save(); ctx.shadowColor = "rgba(54,42,31,0.26)"; @@ -137,7 +137,7 @@ draw(ctx, t, lighting = null) { const stretchX = 1 + impact * 0.05; ctx.scale(stretchX, squashY); if (img) { - const metrics = typeof getImageMetrics === "function" ? getImageMetrics("genkotsu") : null; + const metrics = getImageMetrics("genkotsu"); const w = this.r * 2.55; const h = w * (metrics?.ratio || 1.27); ctx.drawImage(img, -w * 0.5, -h * 0.76, w, h); @@ -165,9 +165,9 @@ draw(ctx, t, lighting = null) { } else if (isPinType(this.type)) { const stuck = this.pinState === "lodged"; const isOshibyo = this.type === "oshibyo"; - const behavior = typeof pinBehaviorFor === "function" ? pinBehaviorFor(this.type) : null; + const behavior = pinBehaviorFor(this.type); const assetId = behavior ? (stuck ? behavior.lodgedAsset : behavior.looseAsset) : (stuck ? "pushpin_stuck" : "pushpin"); - const img = typeof getRenderableImage === "function" ? getRenderableImage(assetId, assetId) : images.get(assetId); + const img = getRenderableImage(assetId, assetId); ctx.save(); if (stuck) ctx.scale((this.pinVisualSide || 1) < 0 ? -1 : 1, 1); if (!stuck) ctx.rotate((this.spin || 0) + Math.PI * 0.5); @@ -175,7 +175,7 @@ draw(ctx, t, lighting = null) { ctx.shadowBlur = 3; ctx.shadowOffsetY = 2; if (img) { - const metrics = typeof getImageMetrics === "function" ? getImageMetrics(assetId) : null; + const metrics = getImageMetrics(assetId); const ratio = metrics?.ratio || (stuck ? 1 : 2.0); const w = isOshibyo ? (stuck ? this.r * 1.55 : this.r * 0.78) : (stuck ? this.r * 2.15 : this.r * 1.78); const h = w * ratio; @@ -297,7 +297,7 @@ draw(ctx, t, lighting = null) { ctx.save(); const flash = clamp(Number(this.sprinklerFlash || 0) || 0, 0, 1); const spin = Number(this.sprinklerSpin || 0) || 0; - const range = globalThis.TarinaiItemDynamicToolSystem?.sprinklerRange ? globalThis.TarinaiItemDynamicToolSystem.sprinklerRange(this) : Math.max(288, (Number(this.r || 28) || 28) * 13.8); + const range = globalThis.TarinaiItemDynamicToolSystem.sprinklerRange ? globalThis.TarinaiItemDynamicToolSystem.sprinklerRange(this) : Math.max(288, (Number(this.r || 28) || 28) * 13.8); const metalA = styleNight ? "#6a7782" : "#d6dde3"; const metalB = styleNight ? "#39434d" : "#8a969f"; const darkMetal = styleNight ? "#1e252b" : "#53606a"; @@ -570,7 +570,7 @@ draw(ctx, t, lighting = null) { ctx.textBaseline = "middle"; ctx.fillText(`${count}`, 0, this.r * 1.25); } else if (this.type === "ant_corpse") { - const img = typeof getRenderableImage === "function" ? getRenderableImage("ant_worker", "ant_worker") : images.get("ant_worker"); + const img = getRenderableImage("ant_worker", "ant_worker"); ctx.save(); ctx.rotate((stableUnit(this.id || this.seed, "ant-corpse-rot") - 0.5) * 0.26); ctx.scale(1, -1); @@ -642,7 +642,7 @@ draw(ctx, t, lighting = null) { } else if (this.type === "fence_v" || this.type === "fence_h" || this.type === "bounce_fence" || this.type === "bounce_fence_v" || this.type === "gate_fence") { const bounce = this.type === "bounce_fence" || this.type === "bounce_fence_v"; const gate = this.type === "gate_fence"; - const angle = typeof itemAngleFor === "function" ? itemAngleFor(this) : (Number(this.angle) || 0); + const angle = itemAngleFor(this); const len = Math.max(112, this.r * 3.55); const thick = Math.max(10, this.r * 0.31); const open = gate && this.gateOpen; @@ -717,7 +717,7 @@ draw(ctx, t, lighting = null) { } ctx.restore(); } else if (this.type === "rotator") { - const angle = typeof itemAngleFor === "function" ? itemAngleFor(this) : (Number(this.angle) || 0); + const angle = itemAngleFor(this); const pb = window.TarinaiPhysicsBodySystem; const powered = pb?.scalar?.(this, "motorOn", true) !== false; const speed = Number((powered ? pb?.scalar?.(this, "motorSpeed", 0) : pb?.scalar?.(this, "spin", 0)) || 0) || 0; @@ -746,7 +746,7 @@ draw(ctx, t, lighting = null) { ctx.textBaseline = "middle"; ctx.fillText(powered ? (speed >= 0 ? "\u21bb" : "\u21ba") : "\u25cb", 0, 0); } else if (this.type === "poison_block") { - const angle = typeof itemAngleFor === "function" ? itemAngleFor(this) : (Number(this.angle) || 0); + const angle = itemAngleFor(this); const pb = window.TarinaiPhysicsBodySystem; const enabled = pb?.scalar?.(this, "solid", true) !== false; const segments = (pb?.segments?.(this) || [[-82, -28, 82, -28], [82, -28, 82, 28], [82, 28, -82, 28], [-82, 28, -82, -28]]); @@ -766,9 +766,9 @@ draw(ctx, t, lighting = null) { // v39.15.53: 毒ブロックは物理輪郭だけを安定描画する。 // 中央の塗りつぶし/文字はキャッシュ更新時に明滅して見えるため描画しない。 } else if (this.type === "reciprocator") { - const bodyAngle = typeof itemAngleFor === "function" ? itemAngleFor(this) : (Number(this.angle) || 0); + const bodyAngle = itemAngleFor(this); const pb = window.TarinaiPhysicsBodySystem; - const axisAngle = window.TarinaiMechanicalSystem?.railAxisAngle?.(this) ?? pb?.scalar?.(this, "railAxis", bodyAngle) ?? bodyAngle; + const axisAngle = window.TarinaiMechanicalSystem.railAxisAngle(this) ?? pb?.scalar?.(this, "railAxis", bodyAngle) ?? bodyAngle; const powered = pb?.scalar?.(this, "railOn", true) !== false; const thick = Math.max(4, Math.min(34, Number(pb?.scalar?.(this, "thickness", 12) || 12) || 12)); const tier = mechanicalRenderTier(); diff --git a/js/item_runtime.js b/js/item_runtime.js index c564796..6ad9b95 100644 --- a/js/item_runtime.js +++ b/js/item_runtime.js @@ -5,8 +5,8 @@ // through the explicit lifecycle pipeline. (function (global) { function installPrototypeBoundary() { - const proto = global.Item?.prototype; - if (!proto || proto._tarinaiItemRuntimeInstalled) return false; + const proto = global.Item.prototype; + if (proto._tarinaiItemRuntimeInstalled) return false; proto.update = function update(dt, worldRef = global.world) { return global.TarinaiItemRuntime.updateOne(this, dt, worldRef); }; @@ -19,36 +19,19 @@ } function updateOne(item, dt, worldRef = global.world) { - if (!item || item.dead || !global.TarinaiItemLifecyclePipeline?.updateOne) return false; + if (!item || item.dead) return false; return global.TarinaiItemLifecyclePipeline.updateOne(item, dt, worldRef) !== false; } function updateScheduled(worldRef, dt) { - if (global.TarinaiItemUpdateScheduler?.run) return global.TarinaiItemUpdateScheduler.run(worldRef, dt); - const helpers = global.TarinaiSimulationRuntime?.helpers; - if (!helpers?.forScheduledItems || !helpers?.itemUpdateInterval) return 0; - let ran = 0; - helpers.forScheduledItems(worldRef, (item) => { - const interval = helpers.itemUpdateInterval(item); - if (!Number.isFinite(interval)) return; - if (interval <= 0) { - if (updateOne(item, dt, worldRef)) ran += 1; - return; - } - item._scheduledUpdateDt = Math.min(10, (item._scheduledUpdateDt || 0) + dt); - if (item._scheduledUpdateDt < interval) return; - const scheduledDt = item._scheduledUpdateDt; - item._scheduledUpdateDt = 0; - if (updateOne(item, scheduledDt, worldRef)) ran += 1; - }); - return ran; + return global.TarinaiItemUpdateScheduler.run(worldRef, dt); } function trackedDynamicItems(worldRef) { - return global.TarinaiItemUpdateScheduler?.trackedDynamicItems?.(worldRef) || worldRef?._itemUpdateRealtime || []; + return global.TarinaiItemUpdateScheduler.trackedDynamicItems(worldRef); } - const api = Object.freeze({ installPrototypeBoundary, updateOne, updateScheduled, trackedDynamicItems }); + const api = Object.freeze({ updateOne, updateScheduled, trackedDynamicItems }); global.TarinaiItemRuntime = api; installPrototypeBoundary(); })(typeof window !== "undefined" ? window : globalThis); diff --git a/js/item_tool_definitions.js b/js/item_tool_definitions.js index f6f3ea9..9f86259 100644 --- a/js/item_tool_definitions.js +++ b/js/item_tool_definitions.js @@ -41,7 +41,7 @@ const TOOL_DEFINITIONS = Object.freeze({ ant_nest: { id: "ant_nest", itemType: "ant_nest", label: "\u30a2\u30ea\u306e\u5de3", placeable: true, scalable: false, radius: 17, amount: 999, icon: "assets/ui/tool_ant_nest.webp", collisionShape: "circle", tooltip: "\u50cd\u304d\u30a2\u30ea\u304c\u305f\u308a\u306a\u3044\u3092\u63a2\u3057\u3001\u5de3\u3078\u904b\u3076\u3002" }, ball: { id: "ball", itemType: "ball", label: "\u30dc\u30fc\u30eb", placeable: true, scalable: false, radius: 18, amount: 999, iconText: "\u26bd", collisionShape: "circle", tooltip: "\u305f\u308a\u306a\u3044\u304c\u904a\u3076\u305f\u3081\u306e\u304a\u3082\u3061\u3083\u3002" }, signboard: { id: "signboard", itemType: "signboard", label: "\u770b\u677f", placeable: true, scalable: false, radius: 30, amount: 999, icon: "assets/ui/tool_signboard.webp", collisionShape: "circle", tooltip: "\u8a2d\u7f6e\u5f8c\u306b\u89b3\u5bdf\u30c4\u30fc\u30eb\u3067\u30af\u30ea\u30c3\u30af\u3059\u308b\u3068\u6587\u5b57\u3092\u66f8\u304d\u8fbc\u3081\u308b\u770b\u677f\u30026\u6587\u5b57\u00d73\u884c\u307e\u3067\u3002" }, - duplicator: { id: "duplicator", itemType: "duplicator", label: "\u8907\u88fd\u6a5f", placeable: true, scalable: false, radius: 34, amount: 999, icon: "assets/ui/tool_duplicator.webp", collisionShape: "circle", tooltip: "\u98df\u3079\u7269\u30fb\u85ac\u30fb\u753b\u92f2\u30fb\u304a\u3057\u308a\u92f2\u3092\u30bb\u30c3\u30c8\u3067\u304d\u308b\u3002\u92f2\u7cfb\u306f\u89e6\u308c\u305f\u305f\u308a\u306a\u3044\u306b\u523a\u3055\u308b\u3002" }, + duplicator: { id: "duplicator", itemType: "duplicator", label: "\u8907\u88fd\u6a5f", placeable: true, scalable: false, radius: 34, amount: 999, icon: "assets/ui/tool_duplicator.webp", collisionShape: "circle", tooltip: "食べ物、薬、鋲をセットすると無限に供給できる。" }, firecracker: { id: "firecracker", itemType: "firecracker", label: "\u7206\u7af9", placeable: true, scalable: true, radius: 15, amount: 999, icon: "assets/ui/tool_firecracker.webp", tooltip: "\u7206\u767a\u3092\u8d77\u3053\u3059\u3002" }, pushpin: { id: "pushpin", itemType: "pushpin", label: "\u753b\u92f2", placeable: true, scalable: false, radius: 8, amount: 999, icon: "assets/ui/tool_pushpin.webp", tooltip: "\u843d\u3068\u3059\u3068\u3053\u308d\u304c\u308a\u3001\u305f\u308a\u306a\u3044\u306b\u523a\u3055\u308b\u3068\u30d1\u30cb\u30c3\u30af\u3068\u7d99\u7d9a\u30c0\u30e1\u30fc\u30b8\u3092\u4e0e\u3048\u308b\u3002" }, oshibyo: { id: "oshibyo", itemType: "oshibyo", label: "\u304a\u3057\u308a\u92f2", placeable: true, scalable: false, radius: 9, amount: 999, icon: "assets/ui/tool_oshibyo.webp", tooltip: "\u523a\u3055\u308b\u3068\u305a\u3093\u3061\u304c\u51fa\u306a\u304f\u306a\u308b\u3002\u3064\u307e\u3093\u3067\u629c\u304f\u3068\u6e9c\u307e\u3063\u305f\u305a\u3093\u3061\u304c\u5f3e\u3051\u98db\u3076\u3002" }, @@ -78,9 +78,6 @@ const LINK_ITEM_TYPES = Object.freeze(new Set(["rope", "rod"])); const PHYSICS_TOOL_IDS = Object.freeze(["rope", "rod", "fence_h", "bounce_fence", "gate_fence", "rotator", "poison_block", "reciprocator"]); const ROTATABLE_ITEM_TYPES = Object.freeze(new Set([...FENCE_ITEM_TYPES, ...MECHANICAL_ITEM_TYPES])); function isFenceItemType(type = "") { return FENCE_ITEM_TYPES.has(String(type || "")); } -function isMechanicalItemType(type = "") { return MECHANICAL_ITEM_TYPES.has(String(type || "")); } -function isLinkItemType(type = "") { return LINK_ITEM_TYPES.has(String(type || "")); } -function isPhysicsToolId(id = "") { return PHYSICS_TOOL_IDS.includes(String(id || "")); } function isRotatableItemType(type = "") { return ROTATABLE_ITEM_TYPES.has(String(type || "")); } function defaultItemAngle(type = "") { const t = String(type || ""); @@ -97,9 +94,6 @@ function itemAngleFor(itemOrType = "") { if (itemOrType && typeof itemOrType === "object") return normalizedItemAngle(itemOrType.angle, defaultItemAngle(itemOrType.type)); return defaultItemAngle(String(itemOrType || "")); } -function rotatableToolIds() { - return Object.values(TOOL_DEFINITIONS).filter(def => isRotatableItemType(def.itemType || def.id)).map(def => def.id); -} function itemHasTrait(type = "", trait = "") { return Boolean(ITEM_TRAITS[trait]?.has?.(String(type || ""))); @@ -193,32 +187,17 @@ function toolTipsFromDefinitions() { Object.assign(global, { - TOOL_DEFINITIONS, - ITEM_TRAITS, - FENCE_ITEM_TYPES, - MECHANICAL_ITEM_TYPES, - LINK_ITEM_TYPES, - PHYSICS_TOOL_IDS, - ROTATABLE_ITEM_TYPES, isFenceItemType, - isMechanicalItemType, - isLinkItemType, - isPhysicsToolId, isRotatableItemType, defaultItemAngle, normalizedItemAngle, itemAngleFor, - rotatableToolIds, itemHasTrait, isPinType, isSleepFurnitureType, - isLodgedPin, lodgedPinFor, - lodgedPinBehaviorFor, hasLodgedPinEffect, - PIN_BEHAVIORS, pinBehaviorFor, - TOOL_CATEGORIES, toolCategories, toolDefinition, toolLabel, @@ -228,28 +207,10 @@ function toolTipsFromDefinitions() { itemAmountFor, TOOL_SIZE_ORDER, TOOL_SIZE_LABELS, - TOOL_SIZE_SCALES, normalizedToolSizeFor, - toolSizeScaleForValue, toolSizeScaleFor, scalableToolIds, toolTipsFromDefinitions, }); - global.TarinaiToolRegistry = Object.freeze({ - definitions: TOOL_DEFINITIONS, - traits: ITEM_TRAITS, - categories: toolCategories, - definition: toolDefinition, - label: toolLabel, - itemType: toolItemType, - item: itemDefinition, - radius: itemRadiusFor, - amount: itemAmountFor, - hasTrait: itemHasTrait, - physicsToolIds: () => [...PHYSICS_TOOL_IDS], - rotatableToolIds, - scalableToolIds, - pinBehavior: pinBehaviorFor, - }); })(typeof window !== "undefined" ? window : globalThis); diff --git a/js/item_type_initializers.js b/js/item_type_initializers.js index 927d53b..a5a5c99 100644 --- a/js/item_type_initializers.js +++ b/js/item_type_initializers.js @@ -4,8 +4,6 @@ function initializeItemTypeState(item, type, x, y) { if (!item) return item; - const bodySchemaVersion = globalThis.TarinaiPhysicsBodySystem?.BODY_SCHEMA_VERSION || 4; - const constraintSchemaVersion = globalThis.TarinaiPhysicsBodySystem?.CONSTRAINT_SCHEMA_VERSION || 4; if (isServingFoodType(type) || ["grass", "ant_corpse", "zunchi"].includes(type)) item.roles.food = true; if (type === "water" || type === "zunda_juice") item.roles.drink = true; if (["sweet", "water", "zunda_juice"].includes(type) || isParamEffectItemType(type)) item.roles.medicine = true; @@ -69,7 +67,6 @@ function initializeItemTypeState(item, type, x, y) { if (type === "ant_nest") { item.antCount = ANT_NEST_START_COUNT || 6; item.antSpawnTimer = rand(0.6, 2.2); - item.antSpawnCooldown = rand(0.8, 2.4); item.antWorkers = typeof buildAntWorkerPool === "function" ? buildAntWorkerPool(item.antCount) : Array.from({ length: item.antCount }, () => ANT_WORKER_HP || 32); @@ -111,8 +108,8 @@ function initializeItemTypeState(item, type, x, y) { item.deletable = true; } - if (typeof isRotatableItemType === "function" && isRotatableItemType(type)) { - item.angle = typeof defaultItemAngle === "function" ? defaultItemAngle(type) : 0; + if (isRotatableItemType(type)) { + item.angle = defaultItemAngle(type); item.amount = Math.max(item.amount || 0, 999); } if (type === "gate_fence") { @@ -122,7 +119,6 @@ function initializeItemTypeState(item, type, x, y) { if (type === "poison_block") { item.physicsBody = { - schema: bodySchemaVersion, type: "poison_block", kind: "passive", pose: { x, y, angle: Number(item.angle) || 0 }, @@ -146,7 +142,6 @@ function initializeItemTypeState(item, type, x, y) { if (type === "rotator") { item.physicsBody = { - schema: bodySchemaVersion, type: "rotator", kind: "rotational", pose: { x, y, angle: Number(item.angle) || 0 }, @@ -166,7 +161,6 @@ function initializeItemTypeState(item, type, x, y) { item.r = 48; item.deletable = true; item.physicsConstraint = { - schema: constraintSchemaVersion, type, kind: type === "rope" ? "flexible-distance" : "rigid-distance", endpoints: [null, null], @@ -178,7 +172,6 @@ function initializeItemTypeState(item, type, x, y) { if (type === "reciprocator") { item.physicsBody = { - schema: bodySchemaVersion, type: "reciprocator", kind: "linear", pose: { x, y, angle: Number(item.angle) || 0 }, @@ -193,9 +186,9 @@ function initializeItemTypeState(item, type, x, y) { item.amount = 999; } - if (globalThis.TarinaiPhysicsBodySystem?.isPhysicsType?.(type)) { - globalThis.TarinaiPhysicsBodySystem.purgeLegacyPhysicsStorage?.(item); - globalThis.TarinaiPhysicsBodySystem.invalidateItem?.(item, "item-initialized"); + if (globalThis.TarinaiPhysicsBodySystem.isPhysicsType(type)) { + globalThis.TarinaiPhysicsBodySystem.purgeLegacyPhysicsStorage(item); + globalThis.TarinaiPhysicsBodySystem.invalidateItem(item, "item-initialized"); } if (isServingFoodType(type)) { diff --git a/js/item_update_policy.js b/js/item_update_policy.js index 96cebc7..c59065e 100644 --- a/js/item_update_policy.js +++ b/js/item_update_policy.js @@ -15,7 +15,7 @@ ]); function mobilePerfHint() { - return Boolean(global.TarinaiInputMode?.snapshot?.().touchFirst || (global.innerWidth || 9999) <= 760); + return Boolean(global.TarinaiInputMode.snapshot().touchFirst || (global.innerWidth || 9999) <= 760); } function itemUpdateInterval(item) { @@ -38,7 +38,7 @@ if (item.type === "trace" || item.type === "splat") return mobile ? 1.8 : 1.2; if (item.type === "ant_corpse") return 3.5; if (typeof global.isServingFoodType === "function" && global.isServingFoodType(item.type)) return 5.0; - if (typeof isServingFoodType === "function" && isServingFoodType(item.type)) return 5.0; + if (isServingFoodType(item.type)) return 5.0; return Infinity; } @@ -75,18 +75,11 @@ return out; } - function isRealtime(item) { - return itemUpdateInterval(item) <= 0; - } global.TarinaiItemUpdatePolicy = Object.freeze({ - REALTIME_ITEM_TYPES, - PIN_ITEM_TYPES, - SCHEDULED_ITEM_TYPES, itemUpdateInterval, forScheduledItems, ensureBuckets, candidateItems, - isRealtime, }); })(typeof window !== "undefined" ? window : globalThis); diff --git a/js/item_update_scheduler.js b/js/item_update_scheduler.js index bad6a44..0554c6a 100644 --- a/js/item_update_scheduler.js +++ b/js/item_update_scheduler.js @@ -1,22 +1,19 @@ "use strict"; (function (global) { - const policy = () => global.TarinaiItemUpdatePolicy || {}; + const policy = global.TarinaiItemUpdatePolicy; function itemUpdateInterval(item) { - const interval = policy().itemUpdateInterval?.(item); + const interval = policy.itemUpdateInterval(item); return Number.isFinite(interval) ? interval : Infinity; } function candidateItems(worldRef) { - if (policy().candidateItems) return policy().candidateItems(worldRef); - return (worldRef?.items || []).filter(it => it && !it.dead && it.type !== "grass"); + return policy.candidateItems(worldRef); } function ensureBuckets(worldRef) { - if (policy().ensureBuckets) return policy().ensureBuckets(worldRef); - if (worldRef?.ensureItemBuckets) worldRef.ensureItemBuckets("item-update-scheduler"); - return undefined; + return policy.ensureBuckets(worldRef); } function heapPush(heap, entry) { @@ -111,8 +108,7 @@ continue; } if (interval <= 0) { - if (global.TarinaiItemRuntime?.updateOne) global.TarinaiItemRuntime.updateOne(item, dt, worldRef); - else item.update(dt, worldRef); + global.TarinaiItemRuntime.updateOne(item, dt, worldRef); item._schedulerLastAt = now; ran += 1; if (Number.isFinite(itemUpdateInterval(item)) && itemUpdateInterval(item) <= 0) nextRealtime.push(item); @@ -143,8 +139,7 @@ continue; } const elapsed = Math.min(10, Math.max(0.001, now - (item._schedulerLastAt ?? entry.time - interval))); - if (global.TarinaiItemRuntime?.updateOne) global.TarinaiItemRuntime.updateOne(item, elapsed, worldRef); - else item.update(elapsed, worldRef); + global.TarinaiItemRuntime.updateOne(item, elapsed, worldRef); item._schedulerLastAt = now; ran += 1; dueRuns += 1; @@ -154,10 +149,10 @@ } function physicsBudgetFor(worldRef) { - const tier = global.TarinaiPerf?.renderQualityTier?.() || "high"; + const tier = global.TarinaiPerf.renderQualityTier(); const itemCount = (worldRef?.items || []).length || 0; const activeHint = worldRef?._itemUpdateRealtime?.length || 0; - const mobile = Boolean(global.TarinaiInputMode?.snapshot?.().touchFirst || (global.innerWidth || 9999) <= 760); + 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 }; @@ -173,25 +168,23 @@ } function run(worldRef, dt) { - const end = global.TarinaiPerf?.begin?.("update.items") || null; + const end = global.TarinaiPerf.begin("update.items"); try { const state = ensure(worldRef); - const realtimeEnd = global.TarinaiPerf?.begin?.("update.items.realtime") || null; + const realtimeEnd = global.TarinaiPerf.begin("update.items.realtime"); let realtimeRan = runRealtime(worldRef, state, dt); let ran = realtimeRan; if (realtimeEnd) realtimeEnd(); - const dueEnd = global.TarinaiPerf?.begin?.("update.items.scheduled") || null; + const dueEnd = global.TarinaiPerf.begin("update.items.scheduled"); const dueRan = runDue(worldRef, state); ran += dueRan; if (dueEnd) dueEnd(); - const physicsEnd = global.TarinaiPerf?.begin?.("update.physicsWorld") || null; + const physicsEnd = global.TarinaiPerf.begin("update.physicsWorld"); const physicsBudget = physicsBudgetFor(worldRef); - const physicsWorld = global.TarinaiPhysicsWorldSystem?.updateWorld - ? global.TarinaiPhysicsWorldSystem.updateWorld(worldRef, dt, physicsBudget) - : null; - const mechanicalWorld = physicsWorld?.mechanical || (global.TarinaiMechanicalSystem?.updateWorld?.(worldRef, dt, { maxPairs: physicsBudget.maxPairs, maxLinks: 0, skipLinks: true, maxSubsteps: physicsBudget.maxSubsteps }) || null); - const constraintWorld = physicsWorld ? physicsWorld.constraintRan : (global.TarinaiConstraintSystem?.updateWorld?.(worldRef, dt, { maxLinks: Math.min(180, physicsBudget.maxLinks) }) || 0); - const postConstraintPairs = physicsWorld ? physicsWorld.postConstraintPairs : (constraintWorld ? (global.TarinaiMechanicalSystem?.resolveMechanicalPairs?.(worldRef, dt, { maxPairs: Math.min(80, physicsBudget.postMaxPairs) }) || 0) : 0); + const physicsWorld = global.TarinaiPhysicsWorldSystem.updateWorld(worldRef, dt, physicsBudget); + const mechanicalWorld = physicsWorld.mechanical || null; + const constraintWorld = physicsWorld.constraintRan || 0; + const postConstraintPairs = physicsWorld.postConstraintPairs || 0; const poisonContactRemoved = worldRef.resolvePoisonBlockItemContacts?.({ maxPoison: 24, maxTargets: 80 }) || 0; if (physicsEnd) physicsEnd(); state.signature = schedulerSignature(worldRef); @@ -212,7 +205,7 @@ mechanicalStats: worldRef._mechanicalWorldStats || null, constraintStats: worldRef._constraintWorldStats || null, }; - return ran + (physicsWorld?.ran || mechanicalWorld?.ran || 0) + constraintWorld + poisonContactRemoved; + return ran + (physicsWorld.ran || mechanicalWorld?.ran || 0) + constraintWorld + poisonContactRemoved; } finally { if (end) end(); } @@ -222,5 +215,5 @@ return worldRef?._itemUpdateRealtime || []; } - global.TarinaiItemUpdateScheduler = Object.freeze({ run, rebuild, ensure, itemUpdateInterval, trackedDynamicItems }); + global.TarinaiItemUpdateScheduler = Object.freeze({ run, trackedDynamicItems }); })(typeof window !== "undefined" ? window : globalThis); diff --git a/js/item_visual_definitions.js b/js/item_visual_definitions.js index 8f85020..75175b9 100644 --- a/js/item_visual_definitions.js +++ b/js/item_visual_definitions.js @@ -119,13 +119,5 @@ function itemVisualDefinition(type = "") { return defs[String(type || "")] || null; } - - function itemVisualPalette(type = "") { - return itemVisualDefinition(type)?.palette || null; - } - - global.TarinaiItemVisualDefinitions = defs; global.itemVisualDefinition = itemVisualDefinition; - global.itemVisualPalette = itemVisualPalette; - global.TarinaiItemVisualRegistry = Object.freeze({ definitions: defs, definition: itemVisualDefinition, palette: itemVisualPalette }); })(typeof window !== "undefined" ? window : globalThis); diff --git a/js/items.js b/js/items.js index 417acc8..6e72a97 100644 --- a/js/items.js +++ b/js/items.js @@ -1,8 +1,8 @@ // Item construction and shared item helpers. Type-specific initialization is in item_type_initializers.js. "use strict"; -const FOOD_SERVING_TYPES = new Set(window.TarinaiItemRegistry?.food?.servingTypes?.() || ["food", "sweet", "love_mochi", "fight_mochi", "sleep_drug", "laxative", "protein", "niteropu", "ammo", "mystery_drug", "mercury", "giant_drug", "dwarf_drug", "zunda_juice"]); -const PARAM_EFFECT_ITEM_TYPES = new Set(window.TarinaiItemRegistry?.food?.paramEffectTypes?.() || ["laxative", "protein", "niteropu", "ammo", "mystery_drug", "mercury", "giant_drug", "dwarf_drug", "zunda_juice"]); +const FOOD_SERVING_TYPES = new Set(window.TarinaiItemRegistry.food?.servingTypes?.() || ["food", "sweet", "love_mochi", "fight_mochi", "sleep_drug", "laxative", "protein", "niteropu", "ammo", "mystery_drug", "mercury", "giant_drug", "dwarf_drug", "zunda_juice"]); +const PARAM_EFFECT_ITEM_TYPES = new Set(window.TarinaiItemRegistry.food?.paramEffectTypes?.() || ["laxative", "protein", "niteropu", "ammo", "mystery_drug", "mercury", "giant_drug", "dwarf_drug", "zunda_juice"]); function isParamEffectItemType(type = "") { return PARAM_EFFECT_ITEM_TYPES.has(type); } @@ -15,7 +15,6 @@ function isServingFoodType(type = "") { return FOOD_SERVING_TYPES.has(type); } const PASSIVE_FOOD_DECAY_PER_SECOND = 0.004; -const PASSIVE_FOOD_HYGIENE_PENALTY_PER_SERVING = 0.055; const GRASS_STAGE_COUNT = 5; const GRASS_STAGE_AMOUNTS = [24, 48, 72, 96, 120]; @@ -96,12 +95,12 @@ function passiveFoodDecayInterval(worldRef = null) { function passiveFoodDecayRate(item) { const size = item?.toolSize || "medium"; const sizeMul = size === "small" ? 0.74 : (size === "large" ? 1.32 : 1); - const typeMul = window.TarinaiItemRegistry?.food?.passiveDecayMultiplier?.(item?.type) ?? (item?.type === "sweet" ? 0.82 : (item?.type === "zunda_juice" ? 1.12 : 1)); + const typeMul = window.TarinaiItemRegistry.food?.passiveDecayMultiplier?.(item?.type) ?? (item?.type === "sweet" ? 0.82 : (item?.type === "zunda_juice" ? 1.12 : 1)); return PASSIVE_FOOD_DECAY_PER_SECOND * sizeMul * typeMul; } function itemVisualFor(type = "") { - return typeof itemVisualDefinition === "function" ? itemVisualDefinition(type) : null; + return itemVisualDefinition(type); } function visualPaletteFor(type = "", fallback = {}) { @@ -250,7 +249,7 @@ function drawBulletSprite(ctx, r) { function isConsumableSpriteType(type = "") { const key = String(type || ""); return ["food", "sweet", "love_mochi", "fight_mochi", "sleep_drug"].includes(key) - || (typeof isParamEffectItemType === "function" && isParamEffectItemType(key)); + || isParamEffectItemType(key); } function drawConsumableFieldSprite(ctx, type = "", r = 10, seed = 1, opts = {}) { @@ -321,7 +320,7 @@ function drawConsumableFieldSprite(ctx, type = "", r = 10, seed = 1, opts = {}) } } else if (key === "sleep_drug") { drawSleepTabletSprite(ctx, localR * visualFieldScaleFor("sleep_drug"), visualPaletteFor("sleep_drug")); - } else if (typeof isParamEffectItemType === "function" && isParamEffectItemType(key)) { + } else if (isParamEffectItemType(key)) { const visual = itemVisualFor(key); const palette = visual?.palette || {}; const renderer = visual?.renderer || "split_pill"; @@ -390,14 +389,14 @@ function drawConsumableFieldSprite(ctx, type = "", r = 10, seed = 1, opts = {}) function drawStoredConsumableSprite(ctx, type = "", r = 10, seed = 1) { const key = String(type || "food"); - if (typeof isPinType === "function" && isPinType(key)) { - const behavior = typeof pinBehaviorFor === "function" ? pinBehaviorFor(key) : null; + if (isPinType(key)) { + const behavior = pinBehaviorFor(key); const assetId = behavior?.looseAsset || key; - const img = typeof getRenderableImage === "function" ? getRenderableImage(assetId, assetId) : images.get(assetId); + const img = getRenderableImage(assetId, assetId); ctx.save(); ctx.rotate(-0.35); if (img) { - const metrics = typeof getImageMetrics === "function" ? getImageMetrics(assetId) : null; + const metrics = getImageMetrics(assetId); const ratio = metrics?.ratio || 2.0; const w = key === "oshibyo" ? r * 1.30 : r * 1.70; const h = w * ratio; @@ -422,7 +421,7 @@ function drawStoredConsumableSprite(ctx, type = "", r = 10, seed = 1) { } function previewItemRadiusFor(type = "") { - const def = typeof itemDefinition === "function" ? itemDefinition(type) : null; + const def = itemDefinition(type); const base = Number(def?.radius || itemRadiusFor?.(type, 16) || 16); if (type === "genkotsu") return 30; if (type === "fence_v" || type === "fence_h" || type === "bounce_fence" || type === "bounce_fence_v" || type === "gate_fence") return 24; @@ -523,8 +522,8 @@ function drawToolItemPreview(ctx, type = "", opts = {}) { fuseTimer: 5, fuseMax: 5, impactFlash: 0, - foodServingsMax: typeof foodServingsForSize === "function" ? foodServingsForSize("medium") : 5, - foodServingsRemaining: typeof foodServingsForSize === "function" ? foodServingsForSize("medium") : 5, + foodServingsMax: foodServingsForSize("medium"), + foodServingsRemaining: foodServingsForSize("medium"), toolSize: "medium", }); if (type === "water") item.amount = 50; @@ -575,7 +574,7 @@ function drawToolItemPreview(ctx, type = "", opts = {}) { else if (["grass", "water", "zunda_juice"].includes(type)) scale *= 1.85; else if (type === "duplicator") scale *= 1.55; else if (["signboard", "bed", "nest_box", "ant_nest", "pushpin", "oshibyo", "fence_v", "fence_h", "bounce_fence", "bounce_fence_v", "gate_fence", "reciprocator", "firecracker", "genkotsu"].includes(type)) scale *= 1.08; - else if (typeof isParamEffectItemType === "function" && isParamEffectItemType(type)) scale *= 1.35; + else if (isParamEffectItemType(type)) scale *= 1.35; if (watermark) scale *= 1.42; ctx.scale(scale, scale); try { @@ -598,14 +597,10 @@ if (typeof window !== "undefined") { window.drawStoredConsumableSprite = drawStoredConsumableSprite; window.drawToolItemPreview = drawToolItemPreview; window.TarinaiGrass = { - stageCount: GRASS_STAGE_COUNT, normalize: normalizeGrassStage, setStage: setGrassStage, advance: advanceGrassStage, regress: regressGrassStage, - amountForStage: grassAmountForStage, - growthForStage: grassGrowthForStage, - stageFromGrowth: grassStageFromGrowth, }; } diff --git a/js/main.js b/js/main.js index 1aa7a5b..a2ac21a 100644 --- a/js/main.js +++ b/js/main.js @@ -9,7 +9,6 @@ function ensureWorldBootstrap() { if (typeof globalThis !== "undefined") globalThis.world = currentWorld; return currentWorld; } catch (error) { - if (typeof window !== "undefined") window.__worldInitError = error?.stack || String(error); throw error; } } @@ -21,19 +20,10 @@ let fpsTimer = 0; let fpsFrames = 0; window.__tarinaiFps = 0; -const LOADING_SPRITES = [ - "assets/sprites/tarinai_01_smile.webp", - "assets/sprites/tarinai_02_angry.webp", - "assets/sprites/tarinai_03_teary.webp", - "assets/sprites/tarinai_04_jito.webp", - "assets/sprites/tarinai_05_drool.webp", - "assets/sprites/tarinai_06_cry.webp", - "assets/sprites/tarinai_07_sleep.webp", - "assets/sprites/tarinai_08_pokan.webp", - "assets/sprites/tarinai_11_weak.webp", - "assets/sprites/tarinai_21_normal_happy.webp", - "assets/sprites/tarinai_29_hungry_70.webp", -]; +const LOADING_SPRITES = SPRITES + .filter(asset => asset && !asset.actionOnly) + .slice(0, 11) + .map(asset => asset.path); let loadingSpriteTimer = 0; function shuffledLoadingSprites() { @@ -94,7 +84,7 @@ function loop() { const rawDt = t - last; const dt = clamp(rawDt, 0, 0.05); last = t; - window.TarinaiPerf?.beginFrame?.(); + window.TarinaiPerf.beginFrame(); fpsTimer += rawDt; fpsFrames += 1; if (fpsTimer >= 0.5) { @@ -102,14 +92,14 @@ function loop() { fpsTimer = 0; fpsFrames = 0; } - const endUpdate = window.TarinaiPerf?.begin?.("update.total") || null; + const endUpdate = window.TarinaiPerf.begin("update.total"); world.update(dt); if (endUpdate) endUpdate(); - const endRender = window.TarinaiPerf?.begin?.("render.total") || null; + const endRender = window.TarinaiPerf.begin("render.total"); render(); if (endRender) endRender(); - window.TarinaiPerf?.endFrame?.(rawDt, window.__tarinaiFps || 0); - if (window.TarinaiPerf?.consumeResizeRequest?.() && typeof resizeCanvas === "function") resizeCanvas(); + window.TarinaiPerf.endFrame(rawDt, window.__tarinaiFps || 0); + if (window.TarinaiPerf.consumeResizeRequest() && typeof resizeCanvas === "function") resizeCanvas(); statsTimer += dt; const statsInterval = 2.5; if (statsTimer > statsInterval) { @@ -121,7 +111,7 @@ function loop() { initLoadingShowcase(); setLoadingProgress("\u521d\u671f\u753b\u50cf\u3092\u8aad\u307f\u8fbc\u307f\u4e2d", 0.08); -loadImages(null, (done, total) => { +loadImages((done, total) => { const p = total > 0 ? done / total : 1; setLoadingProgress(`\u521d\u671f\u753b\u50cf\u3092\u8aad\u307f\u8fbc\u307f\u4e2d ${done}/${total}`, 0.08 + p * 0.46); }).then(() => { @@ -138,7 +128,7 @@ loadImages(null, (done, total) => { render(); renderStats(); syncTopButtons(); - if (typeof startBackgroundImageLoading === "function") startBackgroundImageLoading(); + startBackgroundImageLoading(); setLoadingProgress("\u30b7\u30df\u30e5\u30ec\u30fc\u30b7\u30e7\u30f3\u3092\u958b\u59cb\u4e2d", 0.94); loop(); requestAnimationFrame(hideLoadingScreen); diff --git a/js/math.js b/js/math.js index 37b6179..cf61bdd 100644 --- a/js/math.js +++ b/js/math.js @@ -9,5 +9,3 @@ const distXY = (ax, ay, bx, by) => Math.hypot(ax - bx, ay - by); const nowSec = () => performance.now() / 1000; const fmt = (v) => Math.round(v).toString(); - -window.TarinaiMath = { rand, randi, pick, clamp, lerp, dist, distXY, nowSec, fmt }; diff --git a/js/mechanical_shape_bridge.js b/js/mechanical_shape_bridge.js index dbecf7d..3173c54 100644 --- a/js/mechanical_shape_bridge.js +++ b/js/mechanical_shape_bridge.js @@ -9,24 +9,16 @@ function isMechanicalType(itemOrType) { return Boolean(mech()?.isMechanicalType?.(itemOrType)); } function worldSegments(item) { return mech()?.worldSegments?.(item) || []; } function obstacleRects(item) { return mech()?.obstacleRects?.(item) || []; } - function placementRects(item) { return mech()?.placementRects?.(item) || obstacleRects(item); } - function poisonHazardRects(item) { return mech()?.poisonHazardRects?.(item) || []; } function boundsAabb(item) { return mech()?.boundsAabb?.(item) || null; } - function hazardAabb(item) { return mech()?.hazardAabb?.(item) || null; } function extent(item) { return mech()?.extent?.(item) || 0; } function reach(item) { return mech()?.reach?.(item) || extent(item); } - function hitTest(item, x, y, worldRef, opts = {}) { return mech()?.hitTest?.(item, x, y, worldRef, opts) || { hit: false, distance: Infinity }; } global.TarinaiMechanicalShapeSystem = Object.freeze({ isMechanicalType, worldSegments, obstacleRects, - placementRects, - poisonHazardRects, boundsAabb, - hazardAabb, extent, reach, - hitTest, }); })(typeof window !== "undefined" ? window : globalThis); diff --git a/js/mechanical_system.js b/js/mechanical_system.js index 4293ac0..6e700f5 100644 --- a/js/mechanical_system.js +++ b/js/mechanical_system.js @@ -3,12 +3,86 @@ // Layer: physics/mechanical-body // Common body, footprint, motion, and contact runtime for \u56de\u8ee2\u4f53, \u6bd2\u30d6\u30ed\u30c3\u30af, and \u5f80\u5fa9\u4f53. (function (global) { - const FP = () => global.TarinaiCollisionFootprints || {}; - function num(v, fallback = 0) { const n = Number(v); return Number.isFinite(n) ? n : fallback; } + + function rectCorners(rect, padding = 0) { + const pad = Math.max(0, num(padding)); + if (!rect) return []; + if (!rect.oriented) { + return [ + { x: num(rect.left) - pad, y: num(rect.top) - pad }, + { x: num(rect.right) + pad, y: num(rect.top) - pad }, + { x: num(rect.right) + pad, y: num(rect.bottom) + pad }, + { x: num(rect.left) - pad, y: num(rect.bottom) + pad }, + ]; + } + const c = Number.isFinite(rect.cos) ? rect.cos : Math.cos(num(rect.angle)); + const ss = Number.isFinite(rect.sin) ? rect.sin : Math.sin(num(rect.angle)); + const hw = num(rect.halfW) + pad; + const hh = num(rect.halfH) + pad; + return [[-hw, -hh], [hw, -hh], [hw, hh], [-hw, hh]].map(([lx, ly]) => ({ + x: num(rect.cx) + lx * c - ly * ss, + y: num(rect.cy) + lx * ss + ly * c, + })); + } + + function rectAxes(rect) { + if (!rect?.oriented) return [{ x: 1, y: 0 }, { x: 0, y: 1 }]; + const c = Number.isFinite(rect.cos) ? rect.cos : Math.cos(num(rect.angle)); + const ss = Number.isFinite(rect.sin) ? rect.sin : Math.sin(num(rect.angle)); + return [{ x: c, y: ss }, { x: -ss, y: c }]; + } + + function projectPoints(points, axis) { + let min = Infinity; + let max = -Infinity; + for (const p of points || []) { + const v = num(p.x) * axis.x + num(p.y) * axis.y; + if (v < min) min = v; + if (v > max) max = v; + } + return { min, max }; + } + + function aabbOverlap(a, b, padding = 0) { + const pad = Math.max(0, num(padding)); + return Boolean(a && b && num(a.left) <= num(b.right) + pad && num(a.right) >= num(b.left) - pad + && num(a.top) <= num(b.bottom) + pad && num(a.bottom) >= num(b.top) - pad); + } + + function rectOverlapInfo(a, b, padding = 0) { + if (!aabbOverlap(a, b, padding)) return null; + const ptsA = rectCorners(a, padding); + const ptsB = rectCorners(b, padding); + if (!ptsA.length || !ptsB.length) return null; + let bestOverlap = Infinity; + let bestAxis = null; + for (const axis of [...rectAxes(a), ...rectAxes(b)]) { + const len = Math.hypot(axis.x, axis.y) || 1; + const n = { x: axis.x / len, y: axis.y / len }; + const pa = projectPoints(ptsA, n); + const pb = projectPoints(ptsB, n); + const overlap = Math.min(pa.max, pb.max) - Math.max(pa.min, pb.min); + if (overlap <= 0) return null; + if (overlap < bestOverlap) { bestOverlap = overlap; bestAxis = n; } + } + if (!bestAxis) return null; + const acx = num(a.cx, (num(a.left) + num(a.right)) * 0.5); + const acy = num(a.cy, (num(a.top) + num(a.bottom)) * 0.5); + const bcx = num(b.cx, (num(b.left) + num(b.right)) * 0.5); + const bcy = num(b.cy, (num(b.top) + num(b.bottom)) * 0.5); + if ((bcx - acx) * bestAxis.x + (bcy - acy) * bestAxis.y < 0) bestAxis = { x: -bestAxis.x, y: -bestAxis.y }; + return { + overlap: bestOverlap, + nx: bestAxis.x, + ny: bestAxis.y, + x: (Math.max(num(a.left), num(b.left)) + Math.min(num(a.right), num(b.right))) * 0.5, + y: (Math.max(num(a.top), num(b.top)) + Math.min(num(a.bottom), num(b.bottom))) * 0.5, + }; + } function typeOf(itemOrType) { return typeof itemOrType === "string" ? itemOrType : String(itemOrType?.type || ""); } function isMechanicalType(itemOrType) { const t = typeOf(itemOrType); return t === "rotator" || t === "poison_block" || t === "reciprocator"; } function motionType(itemOrType) { const t = typeOf(itemOrType); return t === "rotator" ? "rotate" : (t === "reciprocator" ? "reciprocate" : (t === "poison_block" ? "passive" : "none")); } @@ -70,7 +144,7 @@ function shapeVersion(item) { const shape = bodyShape(item); - return Number(shape?.version ?? item?._mechanicalShapeVersion ?? item?._segmentsVersion ?? 0) || 0; + return Number(shape?.version ?? item?._mechanicalShapeVersion ?? 0) || 0; } function invalidateGeometry(item) { @@ -84,7 +158,6 @@ } item._mechanicalShapeVersion = nextVersion; item._mechanicalGeomCache = null; - item._mechanicalSupportVersion = nextVersion; item.world?.markSpatialDirty?.("mechanical-geometry-edited"); item.world?.markItemBucketsDirty?.("mechanical-geometry-edited"); return true; @@ -219,8 +292,6 @@ if (cache) { cache.localKey = key; cache.localSegments = out; - cache.displaySegmentCount = normalized.length; - cache.physicsSegmentCount = out.length; } return out; } @@ -298,16 +369,6 @@ return { x: num(rail?.anchorX, ps(item, "railAnchorX", item.x)) + a.x * travel * phase, y: num(rail?.anchorY, ps(item, "railAnchorY", item.y)) + a.y * travel * phase }; } - function phaseFromPosition(item) { - resetAnchor(item); - const a = axis(item); - const travel = halfTravel(item); - const pose = bodyPose(item); - const rail = bodyRail(item); - const dx = num(pose?.x, item.x) - num(rail?.anchorX, ps(item, "railAnchorX", item.x)); - const dy = num(pose?.y, item.y) - num(rail?.anchorY, ps(item, "railAnchorY", item.y)); - return Math.max(-1, Math.min(1, (dx * a.x + dy * a.y) / Math.max(10, travel))); - } function signedDrive(item) { if (!item) return 0; @@ -941,11 +1002,9 @@ function strongestRectContact(rectsA, rectsB, padding = 0) { let best = null; - const overlapInfo = FP().rectOverlapInfo; - if (typeof overlapInfo !== "function") return null; for (const ra of rectsA || []) { for (const rb of rectsB || []) { - const info = overlapInfo(ra, rb, padding); + const info = rectOverlapInfo(ra, rb, padding); if (!info) continue; if (!best || info.overlap > best.info.overlap) best = { ra, rb, info }; } @@ -1044,7 +1103,7 @@ const now = worldRef.time || 0; for (const mechRect of rects) { for (const fenceRect of fences) { - let info = FP().rectOverlapInfo?.(mechRect, fenceRect, item.type === "rotator" ? 1.35 : 0.5); + let info = rectOverlapInfo(mechRect, fenceRect, item.type === "rotator" ? 1.35 : 0.5); if (!info) continue; const separated = applyMechanicalFenceSeparation(item, info, dt, worldRef); if (separated) { @@ -1697,7 +1756,7 @@ function updateMechanicalWorld(worldRef, dt = 0.016, opts = {}) { if (!worldRef?.itemsOfType) return { ran: 0, active: 0, solved: 0, fenceSolved: 0, links: 0 }; const profiler = global.TarinaiPerf; - const end = profiler?.begin?.("update.mechanicalWorld") || null; + const end = profiler.begin("update.mechanicalWorld") || null; try { const rawDt = Number(dt || 0.016) || 0.016; const clampedDt = Math.max(0.001, Math.min(0.05, rawDt)); @@ -1759,7 +1818,7 @@ } // Resolve links after all motors/passive bodies have moved, so constraints see // one coherent world-state rather than each item solving in isolation. - const links = opts.skipLinks === true ? 0 : (global.TarinaiConstraintSystem?.updateWorld?.(worldRef, clampedDt, { maxLinks: opts.maxLinks || 160 }) || 0); + const links = opts.skipLinks === true ? 0 : (global.TarinaiConstraintSystem.updateWorld(worldRef, clampedDt, { maxLinks: opts.maxLinks || 160 }) || 0); // Build a single physics frame after motion and reuse it for pair and fence passes. // This is intentionally save-incompatible: transient body records are runtime only. const frame = buildMechanicalFrame(worldRef, { bodies }); @@ -1799,7 +1858,7 @@ if (item.type === "rotator" && !isPowered(item)) { const a = itemAngle(item); const pose = bodyPose(item); - const side = Math.sign((num(x) - num(pose?.x, item.x)) * Math.cos(a + Math.PI / 2) + (num(y) - num(pose?.y, item.y)) * Math.sin(a + Math.PI / 2)) || ((typeof stableUnit === "function" ? stableUnit(item.id || item.seed || "rotator", "poke-side") : 0.35) < 0.5 ? -1 : 1); + const side = Math.sign((num(x) - num(pose?.x, item.x)) * Math.cos(a + Math.PI / 2) + (num(y) - num(pose?.y, item.y)) * Math.sin(a + Math.PI / 2)) || (stableUnit(item.id || item.seed || "rotator", "poke-side") < 0.5 ? -1 : 1); pset(item, "spin", clamp(num(bodyVelocity(item)?.angular, ps(item, "spin", 0)) * 0.82 + side * 0.32, -1.6, 1.6), "pair-spin-response"); commitBody(item, "poke-passive-rotator"); worldRef?.markSpatialDirty?.("poke-passive-rotator"); @@ -1808,7 +1867,7 @@ if (item.type === "reciprocator" && !isPowered(item)) { const a = axis(item); const pose = bodyPose(item); - const side = Math.sign((num(x) - num(pose?.x, item.x)) * a.x + (num(y) - num(pose?.y, item.y)) * a.y) || ((typeof stableUnit === "function" ? stableUnit(item.id || item.seed || "reciprocator", "poke-side") : 0.65) < 0.5 ? -1 : 1); + const side = Math.sign((num(x) - num(pose?.x, item.x)) * a.x + (num(y) - num(pose?.y, item.y)) * a.y) || (stableUnit(item.id || item.seed || "reciprocator", "poke-side") < 0.5 ? -1 : 1); pset(item, "slideSpeed", clamp(num(bodyVelocity(item)?.linear, ps(item, "slideSpeed", 0)) * 0.76 + side * 34, -115, 115), "pair-slide-response"); commitBody(item, "poke-passive-reciprocator"); worldRef?.markSpatialDirty?.("poke-passive-reciprocator"); @@ -1818,56 +1877,24 @@ } function hitTest(worldRef, item, x, y, opts = {}) { - return FP().hitTestItem?.(worldRef, item, x, y, opts) || { hit: false, distance: Infinity }; + return global.TarinaiCollisionFootprints.hitTestItem(worldRef, item, x, y, opts) || { hit: false, distance: Infinity }; } global.TarinaiMechanicalSystem = Object.freeze({ isMechanicalType, - motionType, isPowered, - itemAngle, sanitizeSegments, - thickness, - extent, - worldSegments, - obstacleRects, placementRects, poisonHazardRects, - boundsAabb, - hazardBoundsAabb, axis, railAxisAngle, - halfTravel, - resetAnchor, - positionFromPhase, - phaseFromPosition, - signedDrive, - pointVelocity, - applyImpulse, - applyPassiveReciprocatorImpulse, - applyPassiveRotatorImpulse, - applyRailCorrection, - reverseReciprocatorOnMechanicalContact, - resolveReciprocatorPhysicalContacts, - repairPoweredReciprocatorStall, - brakePoweredRotatorOnMechanicalContact, applySurfaceVelocityToCircle, - strongestRectContact, - resolvePair, - resolveFenceContacts, resolveInteractions, resolveMechanicalPairs, - resolveMechanicalPairsBroadphase, updateWorld: updateMechanicalWorld, - collectMechanicalBodies, - isMechanicallyActive, - estimatedBodyMotion, - shouldCollideMechanical, - hasInteractionCandidates, updateRotator, updateReciprocator, updatePoisonBlock, - resolvePoisonBlockTarinaiContacts, applyPokeImpulse, hitTest, reach, diff --git a/js/patch_helpers.js b/js/patch_helpers.js deleted file mode 100644 index db2f6c7..0000000 --- a/js/patch_helpers.js +++ /dev/null @@ -1,16 +0,0 @@ -"use strict"; - -(function (global) { - const applied = new Set(); - function patchPrototype(key, owner, patchFn) { - if (!key || !owner?.prototype || typeof patchFn !== "function") return false; - const fullKey = `${owner.name || "Owner"}:${key}`; - if (applied.has(fullKey) || owner.prototype[`__${key}Patched`]) return false; - patchFn(owner.prototype); - applied.add(fullKey); - owner.prototype[`__${key}Patched`] = true; - return true; - } - function patchWorld(key, patchFn) { return patchPrototype(key, global.World, patchFn); } - global.TarinaiPatcher = { patchPrototype, patchWorld, applied }; -})(typeof window !== "undefined" ? window : globalThis); diff --git a/js/perf_profiler.js b/js/perf_profiler.js index 14c3dda..5ff0e22 100644 --- a/js/perf_profiler.js +++ b/js/perf_profiler.js @@ -17,7 +17,7 @@ let frameOpen = null; let frameCount = 0; let lastQualityCheckAt = 0; - const mobilePerfHint = () => Boolean(global.TarinaiInputMode?.snapshot?.().touchFirst || (global.innerWidth || 9999) <= 760); + const mobilePerfHint = () => Boolean(global.TarinaiInputMode.snapshot().touchFirst || (global.innerWidth || 9999) <= 760); let tier = mobilePerfHint() ? "mid" : "high"; let dprScale = mobilePerfHint() ? 0.82 : 1; let resizeRequested = false; @@ -154,7 +154,6 @@ } global.TarinaiPerf = Object.freeze({ - enabled: debugEnabled, begin, beginFrame, endFrame, diff --git a/js/physics_helpers.js b/js/physics_helpers.js index b2db0b5..c877182 100644 --- a/js/physics_helpers.js +++ b/js/physics_helpers.js @@ -3,22 +3,11 @@ (function (global) { function rectLocalPoint(rect, x, y) { - const shared = global.TarinaiCollisionFootprints?.rectLocalPoint; - if (typeof shared === "function") return shared(rect, x, y); - const dx = x - (rect.cx ?? 0); - const dy = y - (rect.cy ?? 0); - const c = Number.isFinite(rect.cos) ? rect.cos : Math.cos(rect.angle || 0); - const ss = Number.isFinite(rect.sin) ? rect.sin : Math.sin(rect.angle || 0); - return { x: dx * c + dy * ss, y: -dx * ss + dy * c }; + return global.TarinaiCollisionFootprints.rectLocalPoint(rect, x, y); } - function rectWorldVector(rect, x, y) { - const shared = global.TarinaiCollisionFootprints?.rectWorldVector; - if (typeof shared === "function") return shared(rect, x, y); - const c = Number.isFinite(rect.cos) ? rect.cos : Math.cos(rect.angle || 0); - const ss = Number.isFinite(rect.sin) ? rect.sin : Math.sin(rect.angle || 0); - return { x: x * c - y * ss, y: x * ss + y * c }; + return global.TarinaiCollisionFootprints.rectWorldVector(rect, x, y); } function circleBounceOffOrientedRect(obj, rect, radius, restitution, worldRef, opts = {}) { @@ -90,7 +79,7 @@ } } if (rect.mechanical) { - global.TarinaiMechanicalSystem?.applySurfaceVelocityToCircle?.(obj, rect, nx, ny, vx, vy, { + global.TarinaiMechanicalSystem.applySurfaceVelocityToCircle(obj, rect, nx, ny, vx, vy, { damping: 1, surfaceScale: rect.rotator ? 0.28 : 0.30, normalBoost: rect.rotator ? 20 : 14, @@ -269,5 +258,5 @@ return speed; } - global.TarinaiPhysics = { bounceInsideWorld, bounceCircleOffRect, resolveKinematicBounceFenceCollision, applyKinematicItemMotion }; + global.TarinaiPhysics = { bounceCircleOffRect, applyKinematicItemMotion }; })(typeof window !== "undefined" ? window : globalThis); diff --git a/js/physics_projection_system.js b/js/physics_projection_system.js index 830e423..99a6c2c 100644 --- a/js/physics_projection_system.js +++ b/js/physics_projection_system.js @@ -6,29 +6,6 @@ (function (global) { function num(v, fallback = 0) { const n = Number(v); return Number.isFinite(n) ? n : fallback; } - function clampInsideWorld(worldRef, obj, radius = 0) { - if (!worldRef || !obj) return false; - const pad = Math.max(CONFIG.worldPadding || 30, num(radius, 0)); - const ox = num(obj.x), oy = num(obj.y); - obj.x = clamp(ox, pad, worldRef.w - pad); - obj.y = clamp(oy, pad, worldRef.h - pad); - return obj.x !== ox || obj.y !== oy; - } - - function projectPair(worldRef, a, b, nx, ny, overlap, weights = {}) { - const amount = Math.max(0, num(overlap)); - if (!worldRef || !a || !b || amount <= 0) return false; - const aw = Number.isFinite(Number(weights.a)) ? Number(weights.a) : 0.5; - const bw = Number.isFinite(Number(weights.b)) ? Number(weights.b) : 0.5; - a.x = num(a.x) - num(nx) * amount * aw; - a.y = num(a.y) - num(ny) * amount * aw; - b.x = num(b.x) + num(nx) * amount * bw; - b.y = num(b.y) + num(ny) * amount * bw; - clampInsideWorld(worldRef, a, a.r || a.radius || 0); - clampInsideWorld(worldRef, b, b.r || b.radius || 0); - return true; - } - function markSpatialDirty(worldRef, item, reason = "projection", threshold = 0.35, minInterval = 0.065) { const world = worldRef || item?.world || null; if (!world || !item) return false; @@ -50,8 +27,6 @@ } global.TarinaiPhysicsProjectionSystem = Object.freeze({ - clampInsideWorld, - projectPair, markSpatialDirty, }); })(typeof window !== "undefined" ? window : globalThis); diff --git a/js/physics_shape_editor_system.js b/js/physics_shape_editor_system.js index 75ca3cf..a0fe33d 100644 --- a/js/physics_shape_editor_system.js +++ b/js/physics_shape_editor_system.js @@ -60,9 +60,6 @@ return Math.hypot(p.x - (x1 + dx * u), p.y - (y1 + dy * u)); } global.TarinaiPhysicsShapeEditorSystem = Object.freeze({ - MODE_LABELS, - TEMPLATE_LABELS, - ACTION_LABELS, decorateToolbar, templateSegments, pointSegmentDistance, diff --git a/js/physics_world_system.js b/js/physics_world_system.js index 46b5408..021b91c 100644 --- a/js/physics_world_system.js +++ b/js/physics_world_system.js @@ -4,8 +4,6 @@ // Central schema, serialization, and orchestration layer for physical tools. // Physical state lives only in item.physicsBody / item.physicsConstraint. (function (global) { - const BODY_SCHEMA_VERSION = 4; - const CONSTRAINT_SCHEMA_VERSION = 4; const BODY_TYPES = new Set(["rotator", "poison_block", "reciprocator"]); const LINK_TYPES = new Set(["rope", "rod"]); @@ -54,13 +52,11 @@ } return out.length ? out : (fallback || [[-78, 0, 78, 0]]).map(seg => seg.slice()); } - function q(value, scale = 1000) { return Math.round(num(value) * scale); } function defaultBody(item) { const type = String(item?.type || ""); const x = num(item?.x), y = num(item?.y), angle = num(item?.angle); return { - schema: BODY_SCHEMA_VERSION, type, kind: defaultKind(type), pose: { x, y, angle }, @@ -80,7 +76,6 @@ const type = String(item?.type || body?.type || ""); const base = defaultBody({ type, x: item?.x, y: item?.y, angle: item?.angle }); const out = body && typeof body === "object" ? body : base; - out.schema = BODY_SCHEMA_VERSION; out.type = type; out.kind = out.kind || base.kind; out.pose = out.pose && typeof out.pose === "object" ? out.pose : base.pose; @@ -160,28 +155,6 @@ function markBodyChanged(item, reason = "physics-body") { if (!item) return; - item._physicsBodyVersion = (Number(item._physicsBodyVersion || 0) || 0) + 1; - item._physicsBodyDirtyReason = reason; - } - - function bodySignature(item) { - const b = ensureBody(item, item?.world || null, { syncFromLegacy: false }); - if (!b) return ""; - const s = b.shape || {}; - const v = b.velocity || {}; - const m = b.motor || {}; - const r = b.rail || {}; - const c = b.collision || {}; - const sl = b.sleep || {}; - return [ - b.type, q(b.pose?.x, 10), q(b.pose?.y, 10), q(b.pose?.angle, 10000), - q(s.thickness, 10), Number(s.version || 0) || 0, Array.isArray(s.segments) ? s.segments.length : 0, - c.solid === false ? 0 : 1, c.hazard === false ? 0 : 1, - m.powered === false ? 0 : 1, q(m.speed, 1000), q(m.direction, 10), - q(v.x, 1000), q(v.y, 1000), q(v.angular, 10000), q(v.linear, 1000), - q(r.axisAngle, 10000), q(r.travel, 10), q(r.phase, 10000), q(r.anchorX, 10), q(r.anchorY, 10), - q(sl.awakeUntil, 1000), q(b.hazard?.damage, 10), q(b.mass, 10), q(b.inertia, 1), - ].join(":"); } function scalar(item, key, fallback = 0) { @@ -247,7 +220,7 @@ } normalizeBody(b, item); markBodyChanged(item, reason); - if (key === "thickness" || key === "solid") global.TarinaiMechanicalSystem?.invalidateGeometry?.(item); + if (key === "thickness" || key === "solid") global.TarinaiMechanicalSystem.invalidateGeometry(item); return true; } @@ -260,7 +233,7 @@ b.shape.segments = cloneSegments(next, defaultSegments(item.type)); b.shape.version = (Number(b.shape.version || 0) || 0) + 1; markBodyChanged(item, reason); - global.TarinaiMechanicalSystem?.invalidateGeometry?.(item); + global.TarinaiMechanicalSystem.invalidateGeometry(item); return true; } function pose(item) { return ensureBody(item, item?.world || null, { syncFromLegacy: false })?.pose || null; } @@ -327,7 +300,6 @@ function defaultConstraint(item) { const x = num(item?.x), y = num(item?.y); return { - schema: CONSTRAINT_SCHEMA_VERSION, type: item?.type, kind: item?.type === "rope" ? "flexible-distance" : "rigid-distance", endpoints: [null, null], @@ -340,7 +312,6 @@ function normalizeConstraint(c, item) { const base = defaultConstraint(item); const out = c && typeof c === "object" ? c : base; - out.schema = CONSTRAINT_SCHEMA_VERSION; out.type = item?.type; out.kind = out.kind || base.kind; out.endpoints = Array.isArray(out.endpoints) ? out.endpoints : [null, null]; @@ -414,12 +385,6 @@ c.particles = Array.isArray(list) ? list : null; return true; } - function constraintSignature(item) { - const c = ensureConstraint(item, item?.world || null, { syncFromLegacy: false }); - if (!c) return ""; - const epSig = ep => !ep ? "null" : [ep.kind || "item", ep.id || "", ep.type || "", q(ep.localX, 10), q(ep.localY, 10), ep.center ? 1 : 0, q(ep.x, 10), q(ep.y, 10), ep.attachT == null ? "" : q(ep.attachT, 1000)].join("/"); - return [item.type || "", q(c.length, 10), q(c.mid?.x, 10), q(c.mid?.y, 10), q(c.mid?.vx, 1000), q(c.mid?.vy, 1000), q(c.sleep?.awakeUntil, 1000), epSig(c.endpoints?.[0]), epSig(c.endpoints?.[1])].join(":"); - } function collectPhysicsItems(worldRef) { const bodies = []; @@ -434,7 +399,7 @@ const { bodies, constraints } = collectPhysicsItems(worldRef); for (const item of bodies) ensureBody(item, worldRef, { syncFromLegacy: item?.playerHeld === true || item?._heldByPlayer === true || item?._physicsExternalPoseDirty === true }); for (const item of constraints) ensureConstraint(item, worldRef, { syncFromLegacy: false }); - const frame = { id: (worldRef._physicsFrameSeq = (Number(worldRef._physicsFrameSeq || 0) || 0) + 1), time: num(worldRef?.time), bodies, constraints, bodyCount: bodies.length, constraintCount: constraints.length, sync: { bodyReads: 0, bodyWrites: 0, constraintReads: 0, constraintWrites: 0 } }; + const frame = { time: num(worldRef?.time), bodies, constraints, bodyCount: bodies.length, constraintCount: constraints.length, sync: { bodyReads: 0, bodyWrites: 0, constraintReads: 0, constraintWrites: 0 } }; worldRef._physicsWorldFrame = frame; return frame; } @@ -455,15 +420,14 @@ function updateWorld(worldRef, dt = 0.016, opts = {}) { if (!worldRef?.itemsOfType) return { ran: 0, bodies: 0, constraints: 0 }; const profiler = global.TarinaiPerf; - const end = profiler?.begin?.("update.physicsWorld.central") || null; + const end = profiler.begin("update.physicsWorld.central") || null; try { const frame = prepareWorld(worldRef, opts); - const mechanicalStats = global.TarinaiMechanicalSystem?.updateWorld?.(worldRef, dt, { maxPairs: opts.maxPairs || 320, maxLinks: 0, skipLinks: true, maxSubsteps: opts.maxSubsteps || 5, focusThreshold: opts.focusThreshold || 44 }) || { ran: 0, active: 0 }; - const constraintRan = global.TarinaiConstraintSystem?.updateWorld?.(worldRef, dt, { maxLinks: opts.maxLinks || 220 }) || 0; - const postConstraintPairs = constraintRan ? (global.TarinaiMechanicalSystem?.resolveMechanicalPairs?.(worldRef, dt, { maxPairs: opts.postMaxPairs || 110 }) || 0) : 0; + const mechanicalStats = global.TarinaiMechanicalSystem.updateWorld(worldRef, dt, { maxPairs: opts.maxPairs || 320, maxLinks: 0, skipLinks: true, maxSubsteps: opts.maxSubsteps || 5, focusThreshold: opts.focusThreshold || 44 }) || { ran: 0, active: 0 }; + const constraintRan = global.TarinaiConstraintSystem.updateWorld(worldRef, dt, { maxLinks: opts.maxLinks || 220 }) || 0; + const postConstraintPairs = constraintRan ? (global.TarinaiMechanicalSystem.resolveMechanicalPairs(worldRef, dt, { maxPairs: opts.postMaxPairs || 110 }) || 0) : 0; const committed = commitWorld(worldRef, frame); - const stats = { ran: (mechanicalStats?.ran || 0) + constraintRan, bodies: frame.bodyCount, constraints: frame.constraintCount, mechanical: mechanicalStats, constraintRan, postConstraintPairs, committed, sync: frame.sync || null, schema: BODY_SCHEMA_VERSION }; - worldRef._physicsWorldStats = stats; + const stats = { ran: (mechanicalStats?.ran || 0) + constraintRan, bodies: frame.bodyCount, constraints: frame.constraintCount, mechanical: mechanicalStats, constraintRan, postConstraintPairs, committed, sync: frame.sync || null }; return stats; } finally { if (end) end(); } } @@ -531,7 +495,7 @@ return endpointToPlain({ kind, id: indexedTarget?.id || ep.id || "", type: ep.type || indexedTarget?.type || "", label: ep.label || indexedTarget?.name || "", liveToken: ep.token ?? indexedTarget?.liveToken ?? null, _ref: indexedTarget || null, localX: num(ep.lx), localY: num(ep.ly), center: !!ep.center, x: num(ep.x, item.x || 0), y: num(ep.y, item.y || 0), attachT: ep.t == null ? null : clamp(num(ep.t) / 1000, 0, 1), fuzzyResolve: !indexedTarget && !(Number.isInteger(refIdx) && refIdx >= 0) }); }; const midArr = Array.isArray(p.mid) ? p.mid : []; - item.physicsConstraint = normalizeConstraint({ schema: CONSTRAINT_SCHEMA_VERSION, type: item.type, kind: p.kind || (item.type === "rope" ? "flexible-distance" : "rigid-distance"), endpoints: [epFromSave(p.endpoints?.[0]), epFromSave(p.endpoints?.[1])], length: Math.max(24, num(p.length, 80)), mid: { x: num(midArr[0], item.x || 0), y: num(midArr[1], item.y || 0), vx: num(midArr[2]) / 10, vy: num(midArr[3]) / 10 }, sleep: { awakeUntil: 0 }, particles: null }, item); + item.physicsConstraint = normalizeConstraint({ type: item.type, kind: p.kind || (item.type === "rope" ? "flexible-distance" : "rigid-distance"), endpoints: [epFromSave(p.endpoints?.[0]), epFromSave(p.endpoints?.[1])], length: Math.max(24, num(p.length, 80)), mid: { x: num(midArr[0], item.x || 0), y: num(midArr[1], item.y || 0), vx: num(midArr[2]) / 10, vy: num(midArr[3]) / 10 }, sleep: { awakeUntil: 0 }, particles: null }, item); return true; } function applyCompactExtra(item, extra, tarinaiList = [], itemList = []) { @@ -544,7 +508,7 @@ if (!item) return false; if (isBodyType(item)) { ensureBody(item, item.world || null, { rebuild: false, syncFromLegacy: false }); - global.TarinaiMechanicalSystem?.invalidateGeometry?.(item); + global.TarinaiMechanicalSystem.invalidateGeometry(item); markBodyChanged(item, reason); return true; } @@ -568,22 +532,19 @@ // Schema normalization entry points. const normalizeBodyState = (item, body = item?.physicsBody) => { if (item && body) { item.physicsBody = normalizeBody(body, item); return item.physicsBody; } return null; }; - const applyBodyState = (item, body = item?.physicsBody, opts = {}) => { if (item && body) { item.physicsBody = normalizeBody(body, item); if (opts.invalidateShape === true) global.TarinaiMechanicalSystem?.invalidateGeometry?.(item); return applyPoseToItem(item); } return false; }; + const applyBodyState = (item, body = item?.physicsBody, opts = {}) => { if (item && body) { item.physicsBody = normalizeBody(body, item); if (opts.invalidateShape === true) global.TarinaiMechanicalSystem.invalidateGeometry(item); return applyPoseToItem(item); } return false; }; const normalizeConstraintState = (item, c = item?.physicsConstraint) => { if (item && c) { item.physicsConstraint = normalizeConstraint(c, item); return item.physicsConstraint; } return null; }; const applyConstraintState = (item, c = item?.physicsConstraint) => { if (item && c) { item.physicsConstraint = normalizeConstraint(c, item); return true; } return false; }; const bodyApi = Object.freeze({ - BODY_SCHEMA_VERSION, CONSTRAINT_SCHEMA_VERSION, BODY_TYPES, LINK_TYPES, - isBodyType, isConstraintType, isPhysicsType, - bodySignature, constraintSignature, + isPhysicsType, ensureBody, ensureConstraint, normalizeBodyState, applyBodyState, markBodyChanged, normalizeConstraintState, applyConstraintState, - prepareWorld, commitWorld, compactItemExtra, applyCompactExtra, purgeLegacyPhysicsStorage, - defaultSegments, cloneSegments, invalidateItem, - pose, velocity, motor, rail, shape, collision, hazard, sleep, - scalar, setScalar, segments, setSegments, syncPoseFromItem, applyPoseToItem, - endpoint, setEndpoint, linkScalar, setLinkScalar, particles, setParticles, endpointToPlain, + compactItemExtra, applyCompactExtra, purgeLegacyPhysicsStorage, + invalidateItem, + scalar, setScalar, segments, setSegments, syncPoseFromItem, + endpoint, setEndpoint, linkScalar, setLinkScalar, particles, setParticles, }); global.TarinaiPhysicsBodySystem = bodyApi; - global.TarinaiPhysicsWorldSystem = Object.freeze({ updateWorld, prepareWorld, commitWorld, collectPhysicsItems, bodyApi }); + global.TarinaiPhysicsWorldSystem = Object.freeze({ updateWorld }); })(typeof window !== "undefined" ? window : globalThis); diff --git a/js/pin_attachment_system.js b/js/pin_attachment_system.js index 90eff95..9138cad 100644 --- a/js/pin_attachment_system.js +++ b/js/pin_attachment_system.js @@ -117,14 +117,9 @@ } global.TarinaiPinAttachmentSystem = Object.freeze({ isPin, - behaviorFor, - isOshibyo, - tarinaiVisualSide, tarinaiAnchor, applyTarinaiPose, - ballAnchor, applyBallPose, - isAttachedToEntity, drawAttachedForEntity, }); })(typeof window !== "undefined" ? window : globalThis); diff --git a/js/placement_preview_system.js b/js/placement_preview_system.js index 593c314..4815d30 100644 --- a/js/placement_preview_system.js +++ b/js/placement_preview_system.js @@ -41,28 +41,17 @@ function orientedAabb(rect) { if (!rect) return null; if (!rect.oriented) return rect; - const fp = global.TarinaiCollisionFootprints; - const pts = fp?.rectCorners?.(rect, 0) || []; - if (!pts.length) { - const reach = Math.max(num(rect.halfW), num(rect.halfH)); - return { left: num(rect.cx) - reach, right: num(rect.cx) + reach, top: num(rect.cy) - reach, bottom: num(rect.cy) + reach }; - } - return { - left: Math.min(...pts.map(p => num(p.x))), - right: Math.max(...pts.map(p => num(p.x))), - top: Math.min(...pts.map(p => num(p.y))), - bottom: Math.max(...pts.map(p => num(p.y))), - }; + const reach = Math.max(num(rect.halfW), num(rect.halfH)); + return { left: num(rect.cx) - reach, right: num(rect.cx) + reach, top: num(rect.cy) - reach, bottom: num(rect.cy) + reach }; } function rectsFor(worldRef, item) { - return (global.TarinaiPlacementCollisionSystem || global.TarinaiCollisionFootprints)?.placementRectsFor?.(worldRef, item) || worldRef?.solidObstacleRects?.(item) || []; + return global.TarinaiCollisionFootprints.placementRectsFor(worldRef, item); } function footprintRadius(worldRef, item, rects = null) { - return (global.TarinaiCollisionFootprints || global.TarinaiPlacementCollisionSystem)?.itemFootprintRadius?.(worldRef, item, rects) - || Math.max(14, num(item?.r, itemRadius(item?.type, 12)) || itemRadius(item?.type, 12)); + return global.TarinaiCollisionFootprints.itemFootprintRadius(worldRef, item, rects); } function ensureMechanicalPreview(item, worldRef) { - if (!item || !global.TarinaiMechanicalSystem?.isMechanicalType?.(item.type)) return item; + if (!item || !global.TarinaiMechanicalSystem.isMechanicalType(item.type)) return item; const pb = global.TarinaiPhysicsBodySystem; pb?.ensureBody?.(item, worldRef || null, { syncFromLegacy: false }); if (item.type === "rotator") { @@ -94,7 +83,7 @@ if (isRotatable(type)) item.angle = opts.angle ?? (worldRef?.toolAngleFor ? worldRef.toolAngleFor(type) : defaultAngle(type)); if (type === "reciprocator") { const axis = item.angle ?? defaultAngle(type); - global.TarinaiPhysicsBodySystem?.setScalar?.(item, "railAxis", axis, "preview-axis"); + global.TarinaiPhysicsBodySystem.setScalar(item, "railAxis", axis, "preview-axis"); item.angle = defaultAngle(type); } ensureMechanicalPreview(item, worldRef); @@ -137,14 +126,6 @@ const rects = rectsFor(worldRef, item); return { rect: null, rects }; } - function blockedFor(worldRef, item, opts = {}) { - const geometry = opts.geometry || geometryFor(worldRef, item) || {}; - const rects = geometry.rects && geometry.rects.length ? geometry.rects : (geometry.rect ? [geometry.rect] : null); - const b = boundsBlocked(worldRef, item, { rects }); - if (b) return true; - if (opts.skipWorldBlocked) return false; - return Boolean(worldRef?.placementBlocked?.(item) || (worldRef?.fencePlacementBlocked?.(item) && worldRef?.isFenceType?.(item.type))); - } function previewForItem(worldRef, item, opts = {}) { if (!worldRef || !item) return null; const geometry = geometryFor(worldRef, item) || {}; @@ -172,12 +153,12 @@ const r = tmp.r; let extra = {}; if (type === "sprinkler") { - const range = global.TarinaiItemDynamicToolSystem?.sprinklerRange?.(tmp) || Math.max(288, r * 13.8); + const range = global.TarinaiItemDynamicToolSystem.sprinklerRange(tmp) || Math.max(288, r * 13.8); extra.influence = { kind: "sprinkler", x, y, radius: range }; extra.boundsRadius = Math.max(14, r * 1.25); } if (type === "grass") { - const overlaps = global.TarinaiPhysicsSoftClear?.collectForPlacement?.(worldRef, tmp) || []; + const overlaps = global.TarinaiPhysicsSoftClear.collectForPlacement(worldRef, tmp) || []; const replacingGrass = overlaps.some(it => it?.type === "grass"); const limitBlocked = !(worldRef.canAddGrass?.(1) ?? true) && !replacingGrass; const softBlocked = Boolean(worldRef.grassBlockedAt?.(x, y, tmp, { ignoreSoftPlacement: true }) || worldRef.grassOnTarinaiAt?.(x, y)); @@ -191,13 +172,8 @@ return { type, x, y, r, rect: geometry.rect || null, rects: geometry.rects || [], influence: extra.influence || null, blocked, item: tmp }; } global.TarinaiPlacementPreviewSystem = Object.freeze({ - makePreviewItem, - rectOutside, - circleOutside, boundsBlocked, clampPointFor, - geometryFor, - blockedFor, forItem: previewForItem, forWorld: previewForWorld, }); diff --git a/js/registry_base.js b/js/registry_base.js index 8ec9bb9..b03b800 100644 --- a/js/registry_base.js +++ b/js/registry_base.js @@ -5,16 +5,9 @@ constructor(defs = {}) { this.defs = new Map(Object.entries(defs || {})); } key(id = "") { return String(id || ""); } get(id = "") { return this.defs.get(this.key(id)) || null; } - has(id = "") { return this.defs.has(this.key(id)); } entries() { return [...this.defs.entries()]; } - values() { return [...this.defs.values()]; } label(id = "", fallback = "") { return this.get(id)?.label || fallback || id || ""; } color(id = "", fallback = "rgba(255,242,160,0.85)") { return this.get(id)?.color || fallback; } - labels() { return Object.fromEntries(this.entries().map(([id, def]) => [id, def.label])); } - colors() { return Object.fromEntries(this.entries().map(([id, def]) => [id, def.color]).filter(([, color]) => color)); } - debugSnapshot(mapper = null) { - return this.entries().map(([id, def]) => mapper ? mapper(id, def) : ({ id, label: def.label || id })); - } } global.TarinaiRegistryBase = TarinaiRegistryBase; })(typeof window !== "undefined" ? window : globalThis); diff --git a/js/render.js b/js/render.js index 7a92663..73b6368 100644 --- a/js/render.js +++ b/js/render.js @@ -7,7 +7,7 @@ const toolCursorIconImages = new Map(); function toolCursorIconDefinition(world) { const tool = String(world?.tool || ""); if (!tool || tool === "observe" || tool === "undo" || tool === "redo" || tool === "shoot") return null; - const def = typeof toolDefinition === "function" ? toolDefinition(tool) : null; + const def = toolDefinition(tool); if (!def) return null; return { tool, def, type: def.itemType || (def.placeable ? tool : "") }; } @@ -236,56 +236,6 @@ function drawToolTargetHoverHighlight(ctx, world) { ctx.restore(); } -function drawShootHoverHighlight(ctx, world) { - if (!world || world.tool !== "shoot") return; - const aim = world.shootAimPoint?.(); - if (!aim?.inside) return; - const target = world.hoverShootTarget; - if (!target || target.dead) return; - ctx.save(); - const pulse = 0.52 + Math.sin((world.time || 0) * 14) * 0.12; - ctx.globalAlpha = 0.72 + pulse * 0.12; - ctx.lineWidth = Math.max(1.8, (world.screenSizeToWorld ? world.screenSizeToWorld(2.0) : 2.0)); - ctx.strokeStyle = "rgba(255,106,92,0.94)"; - ctx.shadowColor = "rgba(255,188,146,0.44)"; - ctx.shadowBlur = Math.max(4, (world.screenSizeToWorld ? world.screenSizeToWorld(6) : 6)); - const isTarinaiTarget = typeof Tarinai !== "undefined" && target instanceof Tarinai; - const isItemTarget = Boolean(target.type); - if (isTarinaiTarget) { - const r = Math.max(16, target.radius || 22) * 1.12; - ctx.beginPath(); - ctx.ellipse(target.x || 0, target.y || 0, r * 0.95, r * 0.76, 0, 0, Math.PI * 2); - ctx.stroke(); - } else if (isItemTarget) { - const rects = world.solidObstacleRects?.(target) || []; - if (target.type === "rope" || target.type === "rod") { - const runtime = world.TarinaiLinkRuntime || TARINAI_RENDER_GLOBAL.TarinaiLinkRuntime; - const pb = TARINAI_RENDER_GLOBAL.TarinaiPhysicsBodySystem; - const a = runtime?.endpointWorld?.(pb?.endpoint?.(target, 0), world); - const b = runtime?.endpointWorld?.(pb?.endpoint?.(target, 1), world); - if (a && b) { - ctx.beginPath(); - ctx.moveTo(a.x, a.y); - ctx.lineTo(b.x, b.y); - ctx.stroke(); - } - } else if (rects.length) { - for (const rect of rects.slice(0, 12)) strokeWorldObstacleRect(ctx, rect); - } else { - const r = Math.max(12, target.r || 14) * (target.type === "ball" ? 1.24 : 1.14); - ctx.beginPath(); - ctx.ellipse(target.x || 0, target.y || 0, r, r * 0.76, 0, 0, Math.PI * 2); - ctx.stroke(); - } - } else { - const r = Math.max(8, target.r || 8) * 1.45; - ctx.beginPath(); - ctx.ellipse(target.x || 0, target.y || 0, r, r * 0.86, 0, 0, Math.PI * 2); - ctx.stroke(); - } - ctx.restore(); -} - function drawShootCursorFallback(ctx, x, y, size) { const half = size * 0.5; const tick = size * 0.16; @@ -324,7 +274,7 @@ function drawShootCursorOverlay(ctx, world) { const canvasW = world.viewportW || world.w || ctx.canvas?.width || 1; const canvasH = world.viewportH || world.h || ctx.canvas?.height || 1; const size = Math.max(90, Math.min(160, Math.round(Math.min(canvasW, canvasH) * 0.205))); - const def = typeof toolDefinition === "function" ? toolDefinition("shoot") : null; + const def = toolDefinition("shoot"); const img = cachedToolCursorIconImage(def?.icon || ""); ctx.save(); ctx.globalAlpha = 0.95; @@ -489,7 +439,7 @@ function drawDeviceInfluenceShape(ctx, influence, blocked = false, alphaScale = } function placementPreviewFor(world) { - return TARINAI_RENDER_GLOBAL.TarinaiPlacementPreviewSystem?.forWorld?.(world) || null; + return TARINAI_RENDER_GLOBAL.TarinaiPlacementPreviewSystem.forWorld(world) || null; } @@ -561,7 +511,7 @@ function drawOperationToolPreview(ctx, world) { const type = world.copyBuffer.type || ""; const item = world.copyPreviewItemAt?.(p.x, p.y); if (item) { - const placementInfo = TARINAI_RENDER_GLOBAL.TarinaiPlacementPreviewSystem?.forItem?.(world, item) || null; + const placementInfo = TARINAI_RENDER_GLOBAL.TarinaiPlacementPreviewSystem.forItem(world, item) || null; const previewRects = placementInfo?.rects || world.solidObstacleRects?.(item) || []; const boundsBlocked = Boolean(placementInfo?.boundsBlocked); const blocked = Boolean(placementInfo ? placementInfo.blocked : (world.placementBlocked?.(item) || world.fencePlacementBlocked?.(item))); @@ -727,7 +677,6 @@ function backgroundCacheKey(world, lighting) { function invalidateRenderCaches(reason = "manual") { if (backgroundCache) backgroundCache.key = ""; - if (terrainCache) terrainCache.key = ""; if (terrainChunkCache) { terrainChunkCache.key = ""; terrainChunkCache.chunks?.clear?.(); @@ -738,13 +687,6 @@ function invalidateRenderCaches(reason = "manual") { // static visual noise. Rendering them into visible chunks avoids repeating // thousands of small strokes every frame and avoids redrawing the entire field // when a small terrain area changes. -const terrainCache = { - canvas: null, - ctx: null, - w: 0, - h: 0, - key: "", -}; const terrainChunkCache = { size: 256, @@ -837,7 +779,7 @@ function redrawTerrainChunk(chunk, worldRef, lighting) { } function drawTerrainLayer(ctx, worldRef, lighting, visibleRect) { - const end = window.TarinaiPerf?.begin?.("render.terrain") || null; + const end = window.TarinaiPerf.begin("render.terrain"); try { const key = terrainCacheKey(worldRef, lighting); if (terrainChunkCache.key !== key || worldRef.terrainDirtyGlobal || (worldRef.terrainDirty && !worldRef.terrainDirtyChunks?.size)) { @@ -871,38 +813,6 @@ function drawTerrainLayer(ctx, worldRef, lighting, visibleRect) { } } -function ensureTerrainCache(worldRef, lighting) { - // Compatibility fallback for older callers. The main renderer uses chunks. - const w = Math.max(1, Math.floor(worldRef.w || 1)); - const h = Math.max(1, Math.floor(worldRef.h || 1)); - const key = `${terrainCacheKey(worldRef, lighting)}:fallback:v${worldRef.terrainVersion || 0}`; - if (!terrainCache.canvas) { - terrainCache.canvas = document.createElement("canvas"); - terrainCache.ctx = terrainCache.canvas.getContext("2d"); - } - if (!worldRef.terrainDirty && terrainCache.w === w && terrainCache.h === h && terrainCache.key === key) return terrainCache.canvas; - terrainCache.w = w; - terrainCache.h = h; - terrainCache.key = key; - terrainCache.canvas.width = w; - terrainCache.canvas.height = h; - const c = terrainCache.ctx; - c.clearRect(0, 0, w, h); - const oldPointer = worldRef.pointer; - try { - if (oldPointer) worldRef.pointer = { ...oldPointer, inside: false }; - for (const it of worldRef.items || []) if (isCachedTerrainItem(it)) it.draw(c, worldRef.time || 0, lighting); - worldRef.terrainDirty = false; - return terrainCache.canvas; - } catch (err) { - console.error("terrain cache redraw failed", err); - terrainCache.key = ""; - return null; - } finally { - if (oldPointer) worldRef.pointer = oldPointer; - } -} - function visibleWorldRect(worldRef, margin = 160) { const scale = worldRef.viewScale ? worldRef.viewScale() : 1; const vw = (worldRef.viewportW || worldRef.w || 1000) / Math.max(0.01, scale); @@ -921,7 +831,7 @@ function renderRadiusForEntity(entity) { if (!entity) return 40; if (entity.type === "genkotsu") return Math.max(420, (entity.r || 88) * 6.2); if (entity.type === "fence_v" || entity.type === "fence_h" || entity.type === "bounce_fence" || entity.type === "bounce_fence_v" || entity.type === "gate_fence") return Math.max(96, (entity.r || 24) * 4.2); - if (entity.type === "reciprocator") return Math.max(140, TARINAI_RENDER_GLOBAL.TarinaiMechanicalSystem?.reach?.(entity) || ((entity.r || 64) * 2.8 + (TARINAI_RENDER_GLOBAL.TarinaiPhysicsBodySystem?.scalar?.(entity, "railTravel", 150) || 150))); + if (entity.type === "reciprocator") return Math.max(140, TARINAI_RENDER_GLOBAL.TarinaiMechanicalSystem.reach(entity) || ((entity.r || 64) * 2.8 + (TARINAI_RENDER_GLOBAL.TarinaiPhysicsBodySystem.scalar(entity, "railTravel", 150) || 150))); if (entity.type === "nest_box") return Math.max(86, (entity.r || 34) * 3.2); if (entity.type === "ant_nest") return Math.max(72, (entity.r || 28) * 3.0); if (entity.kind === "queen") return 42; @@ -939,15 +849,6 @@ function isEntityVisibleInRect(entity, rect, extra = 0) { return x + r >= rect.left && x - r <= rect.right && y + r >= rect.top && y - r <= rect.bottom; } -function renderStackSignature(worldRef) { - return [ - (worldRef.items || []).length, - (worldRef.tarinai || []).length, - (worldRef.ants || []).length, - worldRef.terrainVersion || 0, - ].join(":"); -} - function syncCarriedPlushieToOwner(it, worldRef) { if (!it || !worldRef || !it.isStructure || it.type !== "plushie" || !it.carriedById) return false; const owner = (worldRef.tarinai || []).find(t => t && !t.dead && t.id === it.carriedById); @@ -960,54 +861,9 @@ function syncCarriedPlushieToOwner(it, worldRef) { return Math.hypot((it.x || 0) - (ox || 0), (it.y || 0) - (oy || 0)) > 0.25; } -function ensureWorldRenderStack(worldRef) { - if (!worldRef._renderStack) { - worldRef._renderStack = { signature: "", backItems: [], layered: [], carriedPlushies: [], lodgedPins: [], ballLodgedPins: [] }; - } - const stack = worldRef._renderStack; - const signature = renderStackSignature(worldRef); - if (!worldRef.drawListDirty && stack.signature === signature) return stack; - - stack.backItems.length = 0; - stack.layered.length = 0; - stack.carriedPlushies.length = 0; - stack.lodgedPins.length = 0; - if (stack.ballLodgedPins) stack.ballLodgedPins.length = 0; - - for (const it of worldRef.items || []) { - if (!it || it.dead || isCachedTerrainItem(it)) continue; - if (isPinType(it.type) && it.pinState === "lodged") { - stack.lodgedPins.push(it); - continue; - } - if (isPinType(it.type) && it.pinState === "ball_lodged") { - (stack.ballLodgedPins || (stack.ballLodgedPins = [])).push(it); - continue; - } - if (it.isStructure && it.type === "plushie" && it.carriedById) { - stack.carriedPlushies.push(it); - continue; - } - if (isBehindSpriteLayerItem(it)) stack.backItems.push(it); - else stack.layered.push({ entity: it, y: renderLayerSortY(it), rank: renderLayerKindRank(it) }); - } - for (const t of worldRef.tarinai || []) { - if (t && !t.dead) stack.layered.push({ entity: t, y: renderLayerSortY(t), rank: renderLayerKindRank(t) }); - } - for (const a of worldRef.ants || []) { - if (a && !a.dead) stack.layered.push({ entity: a, y: renderLayerSortY(a), rank: renderLayerKindRank(a) }); - } - - stack.backItems.sort((a, b) => renderLayerSortY(a) - renderLayerSortY(b) || (a.x || 0) - (b.x || 0)); - stack.layered.sort((a, b) => a.y - b.y || a.rank - b.rank || ((a.entity.x || 0) - (b.entity.x || 0))); - stack.signature = signature; - worldRef.drawListDirty = false; - return stack; -} - function collectVisibleRenderStack(worldRef, visibleRect) { - const end = window.TarinaiPerf?.begin?.("render.stackBuild") || null; + const end = window.TarinaiPerf.begin("render.stackBuild"); try { if (!worldRef._visibleRenderStack) worldRef._visibleRenderStack = { backItems: [], layered: [], carriedPlushies: [], lodgedPins: [], ballLodgedPins: [] }; const stack = worldRef._visibleRenderStack; @@ -1021,11 +877,11 @@ function collectVisibleRenderStack(worldRef, visibleRect) { if (it && it.isStructure && it.type === "plushie" && it.carriedById) syncCarriedPlushieToOwner(it, worldRef); if (!it || it.dead || seen.has(it) || isCachedTerrainItem(it) || !isEntityVisibleInRect(it, visibleRect)) return; seen.add(it); - if (typeof isPinType === "function" && isPinType(it.type) && it.pinState === "lodged") { + if (isPinType(it.type) && it.pinState === "lodged") { stack.lodgedPins.push(it); return; } - if (typeof isPinType === "function" && isPinType(it.type) && it.pinState === "ball_lodged") { + if (isPinType(it.type) && it.pinState === "ball_lodged") { (stack.ballLodgedPins || (stack.ballLodgedPins = [])).push(it); return; } @@ -1506,7 +1362,7 @@ function weatherLabel(weather) { } function renderPerfBegin(label) { - return window.TarinaiPerf?.begin?.(label) || null; + return window.TarinaiPerf.begin(label); } function renderPerfEnd(end) { @@ -1578,9 +1434,9 @@ function render() { // Render only entities that live in visible spatial cells; this keeps large offscreen colonies cheap. const renderStack = collectVisibleRenderStack(world, visibleRect); - window.TarinaiPerf?.setMetric?.("render.visibleBackItems", renderStack.backItems.length); - window.TarinaiPerf?.setMetric?.("render.visibleLayered", renderStack.layered.length); - window.TarinaiPerf?.setMetric?.("render.visibleEffects", (world.effects || []).length); + window.TarinaiPerf.setMetric("render.visibleBackItems", renderStack.backItems.length); + window.TarinaiPerf.setMetric("render.visibleLayered", renderStack.layered.length); + window.TarinaiPerf.setMetric("render.visibleEffects", (world.effects || []).length); const endBackItems = renderPerfBegin("render.draw.backItems"); for (const it of renderStack.backItems) { @@ -1593,7 +1449,7 @@ function render() { const drawnAttachedPinIds = new Set(); const drawAttachedPinsForEntity = (entity) => { if (!entity) return; - if (TARINAI_RENDER_GLOBAL.TarinaiPinAttachmentSystem?.drawAttachedForEntity) { + if (TARINAI_RENDER_GLOBAL.TarinaiPinAttachmentSystem.drawAttachedForEntity) { TARINAI_RENDER_GLOBAL.TarinaiPinAttachmentSystem.drawAttachedForEntity({ renderStack, entity, visibleRect, worldRef: world, time: world.time, drawnIds: drawnAttachedPinIds, isVisible: isEntityVisibleInRect, ctx, lighting }); return; } @@ -1791,7 +1647,7 @@ function drawSelectedCard(ctx, world, lighting) { ctx.font = "10px ui-rounded, sans-serif"; const child = t.juvenile && t.parentToFollow?.() ? " / \u5b50" : ""; const zHint = t.digest > 4.9 ? " / \u305a\u3093\u3061" : ""; - ctx.fillText(`${stateLabel(t.state)}${child}${zHint}`, x + 10, y + 35); + ctx.fillText(`${window.TEXT_CATALOG?.stateLabel?.(t.state) || t.state || ""}${child}${zHint}`, x + 10, y + 35); const genLabel = (t.displayGeneration && t.displayGeneration()) ? ` \u4e16\u4ee3 ${t.generation}` : ""; ctx.fillText(`\u7dcf\u5408\u7684\u6c17\u5206 ${fmt(t.mood)}${genLabel} \u4f53\u529b ${fmt(t.energy)}/${fmt(typeof tarinaiMaxEnergy === "function" ? tarinaiMaxEnergy(t) : (Number(t.maxEnergy) || 100))}`, x + 10, y + 52); ctx.fillText(`\u30b9\u30c8\u30ec\u30b9 ${fmt(t.stress)} \u7a7a\u8179 ${fmt(t.hunger)}`, x + 10, y + 68); @@ -1809,4 +1665,4 @@ function roundedRect(ctx, x, y, w, h, r) { } -window.TarinaiRender = { invalidateRenderCaches, randSeed, roundedBlob, getLightingState, projectedShadowParams, imageVisibleBottomFromTop, drawImageProjectedShadow, drawProjectedShadow, drawRadialGlow, drawItemGlow, drawCreatureGlow, drawDawnRimLight, drawGardenBackdrop, drawGardenBed, drawLightRays, drawAtmosphericLighting, drawRain, fillScreenFallback, drawSelectedCard, render, roundedRect }; +window.TarinaiRender = { invalidateRenderCaches }; diff --git a/js/restore_coordinator.js b/js/restore_coordinator.js index 8dc7fff..b8880d8 100644 --- a/js/restore_coordinator.js +++ b/js/restore_coordinator.js @@ -25,9 +25,5 @@ return result; } - global.TarinaiRestoreCoordinator = { - restoreSnapshot, - syncRestoreDependents, - renderWorldAfterRestore, - }; + global.TarinaiRestoreCoordinator = { restoreSnapshot }; })(typeof window !== "undefined" ? window : globalThis); diff --git a/js/save_codec.js b/js/save_codec.js index d4a658d..4ec7ac6 100644 --- a/js/save_codec.js +++ b/js/save_codec.js @@ -12,6 +12,10 @@ BINARY_SCHEMA_VERSION, FIELD_IDS, GROUND_IDS, + MOOD_IDS, + LEGACY_MOOD_IDS, + canonicalMoodId, + enumIndex, ITEM_TYPE_IDS, STRUCTURE_TYPE_IDS, SERVING_FOOD_TYPE_IDS, @@ -249,15 +253,7 @@ return bytes; } - function writeFixed(writer, arr = [], count = 0, signed = true) { - for (let i = 0; i < count; i++) signed ? writer.s(arr[i] || 0) : writer.u(arr[i] || 0); - } - function readFixed(reader, count = 0, signed = true) { - const out = []; - for (let i = 0; i < count; i++) out.push(signed ? reader.s() : reader.u()); - return out; - } - function writeFixedPacked(writer, arr = [], count = 0, bits = 8, step = 1) { + function writeFixedPacked(writer, arr = [], count = 0, bits = 8, step = 1) { writeQuantizedBitFields(writer, arr, count, bits, step); } function readFixedPacked(reader, count = 0, bits = 8, step = 1) { @@ -298,57 +294,8 @@ return out; } - function generatedNameCode(value = "") { - const name = String(value || ""); - if (!name.startsWith("\u306f")) return -1; - const stop = name.endsWith("\u3063"); - const core = name.slice(1, stop ? -1 : undefined); - if (core.length > 6) return -1; - let bits = 0; - for (let i = 0; i < core.length; i++) { - const ch = core.charAt(i); - if (ch !== "\u3046" && ch !== "\u3045") return -1; - if (ch === "\u3045") bits |= (1 << i); - } - return core.length | (stop ? 8 : 0) | (bits << 4); - } - function nameFromGeneratedCode(code = 0) { - const middleLen = code & 7; - const stop = !!(code & 8); - const bits = code >> 4; - let name = "\u306f"; - for (let i = 0; i < middleLen; i++) name += (bits & (1 << i)) ? "\u3045" : "\u3046"; - if (stop) name += "\u3063"; - return name; - } - function writeName(writer, value = "") { - const code = generatedNameCode(value); - if (code >= 0) { - writer.u(code + 1); - return; - } - writer.u(0); - writer.str(value || ""); - } - function readName(reader) { - const code = reader.u(); - if (code > 0) return nameFromGeneratedCode(code - 1); - return reader.str(); - } - function valuesDiffer(a, b) { return (Number(a) || 0) !== (Number(b) || 0); } - function writeFixedDiff(writer, base = [], current = [], count = 0, signed = false) { - let mask = 0; - for (let i = 0; i < count; i++) if (valuesDiffer(current?.[i], base?.[i])) mask |= (1 << i); - writer.u(mask); - for (let i = 0; i < count; i++) if (mask & (1 << i)) signed ? writer.s(current[i] || 0) : writer.u(current[i] || 0); - } - function readFixedDiff(reader, base = [], count = 0, signed = false) { - const out = Array.from({ length: count }, (_, i) => base?.[i] || 0); - const mask = reader.u(); - for (let i = 0; i < count; i++) if (mask & (1 << i)) out[i] = signed ? reader.s() : reader.u(); - return out; - } - function writeTimers(writer, timers = []) { + function valuesDiffer(a, b) { return (Number(a) || 0) !== (Number(b) || 0); } + function writeTimers(writer, timers = []) { const defaults = [0, 0, 0, 0, 0, 0, 0, 0, 100]; let mask = 0; for (let i = 0; i < defaults.length; i++) if (valuesDiffer(timers?.[i], defaults[i])) mask |= (1 << i); @@ -407,7 +354,7 @@ function seedPersonalityArray(seed = "", flags = 0) { // \u305a\u3093\u3061\u3069\u308c\u3044\u306f\u8a95\u751f\u6642\u6027\u683c\u3082\u5168\u8ef8 -1 \u306a\u306e\u3067\u3001\u5dee\u5206\u57fa\u6e96\u30820\u56fa\u5b9a\u306b\u3059\u308b\u3002 if ((Number(flags) || 0) & (1 << 5)) return [0, 0, 0, 0]; - return global.TarinaiSeedFactory?.personalityArray?.(seed) || [100, 100, 100, 100]; + return global.TarinaiSeedFactory.personalityArray(seed) || [100, 100, 100, 100]; } function writeTarinai(writer, row = [], coordState = null) { @@ -618,10 +565,14 @@ function decodeBinarySnapshot(bytes) { const reader = new BinaryReader(bytes); const schema = reader.b(); - if (schema !== BINARY_SCHEMA_VERSION) throw new Error("unsupported binary save schema"); + if (schema !== BINARY_SCHEMA_VERSION && schema !== 15) throw new Error("unsupported binary save schema"); const snapshotVersion = reader.u(); - if (snapshotVersion !== Snapshot.version) throw new Error("unsupported binary save version"); + if (snapshotVersion !== Snapshot.version && snapshotVersion !== 11) throw new Error("unsupported binary save version"); const w = readWorld(reader); + if (schema === 15) { + const legacyMood = LEGACY_MOOD_IDS?.[Number(w[2]) | 0] || "relaxed"; + w[2] = enumIndex(MOOD_IDS, canonicalMoodId?.(legacyMood) || "relaxed"); + } const tCount = reader.u(); const t = []; const tarCoordState = { x: 0, y: 0 }; diff --git a/js/save_schema.js b/js/save_schema.js index 3f6ecb4..1644f99 100644 --- a/js/save_schema.js +++ b/js/save_schema.js @@ -4,13 +4,13 @@ // Owns stable save identifiers shared by snapshot_system.js and save_codec.js. // Current-format save identifiers only. (function (global) { - const SNAPSHOT_VERSION = 11; - const BINARY_SCHEMA_VERSION = 15; + const SNAPSHOT_VERSION = 12; + const BINARY_SCHEMA_VERSION = 16; const FIELD_IDS = Object.freeze(["garden", "cage", "park"]); const GROUND_IDS = Object.freeze(["soil", "dirt", "concrete", "blanket", "foot_massage", "ice", "laboratory"]); const WEATHER_IDS = Object.freeze(["sunny", "cloudy", "light_rain"]); - const MOOD_IDS = Object.freeze(["relaxed", "calm", "prickly", "stinky", "weaklings", "zunda_party", "ball_party", "sun_party", "ant_overrun"]); + const MOOD_IDS = Object.freeze(["relaxed", "devastation", "crisis", "confusion", "fearful", "disease_spread", "weakened", "breeding", "happy", "overcrowded", "isolated"]); const STATE_IDS = Object.freeze(["idle", "sleep", "panic", "sunbath", "fight", "seek_food", "seek_bed", "wander"]); const POWER_MODE_IDS = Object.freeze(["", "protein", "niteropu"]); const SIZE_MODE_IDS = Object.freeze(["", "giant_drug", "dwarf_drug"]); @@ -31,9 +31,6 @@ const STRUCTURE_TYPE_IDS = typeIds(["grass_bed", "plushie"]); const SERVING_FOOD_TYPE_IDS = typeIds(["sweet", "love_mochi", "fight_mochi", "sleep_drug", "laxative", "protein", "niteropu", "mystery_drug", "mercury", "giant_drug", "dwarf_drug", "zunda_juice", "food"]); const PIN_TYPE_IDS = typeIds(["pushpin", "oshibyo"]); - const FENCE_ITEM_TYPE_IDS = typeIds(["fence_v", "fence_h", "bounce_fence", "bounce_fence_v", "gate_fence"]); - const MECHANICAL_ITEM_TYPE_IDS = typeIds(["rotator", "poison_block", "reciprocator"]); - const LINK_ITEM_TYPE_IDS = typeIds(["rope", "rod"]); const JSON_EXTRA_ITEM_TYPE_IDS = typeIds(["rotator", "poison_block", "reciprocator", "rope", "rod"]); const ROTATABLE_ITEM_TYPE_IDS = typeIds(["fence_v", "fence_h", "bounce_fence", "bounce_fence_v", "gate_fence", "rotator", "poison_block", "reciprocator"]); @@ -42,17 +39,30 @@ return i >= 0 ? i : fallback; } + const LEGACY_MOOD_IDS = Object.freeze(["relaxed", "calm", "prickly", "stinky", "weaklings", "zunda_party", "ball_party", "sun_party", "ant_overrun"]); + const LEGACY_MOOD_ALIASES = Object.freeze({ + calm: "relaxed", + prickly: "confusion", + stinky: "disease_spread", + weaklings: "weakened", + zunda_party: "happy", + ball_party: "happy", + sun_party: "happy", + ant_overrun: "fearful", + }); + function canonicalMoodId(value = "relaxed") { + const id = String(value || "relaxed"); + return LEGACY_MOOD_ALIASES[id] || id; + } + function enumValue(list, index, fallback = "") { const i = Number(index) | 0; return list[i] ?? fallback ?? list[0] ?? ""; } - function itemTypeId(type, fallback = -1) { - return enumIndex(ITEM_TYPE_IDS, type, fallback); - } - - function itemTypeValue(index, fallback = "") { - return enumValue(ITEM_TYPE_IDS, index, fallback); + function moodValue(index, fallback = "relaxed") { + const i = Number(index) | 0; + return canonicalMoodId(MOOD_IDS[i] ?? LEGACY_MOOD_IDS[i] ?? fallback); } global.TarinaiSaveSchema = Object.freeze({ @@ -62,6 +72,10 @@ GROUND_IDS, WEATHER_IDS, MOOD_IDS, + LEGACY_MOOD_IDS, + LEGACY_MOOD_ALIASES, + canonicalMoodId, + moodValue, STATE_IDS, POWER_MODE_IDS, SIZE_MODE_IDS, @@ -72,14 +86,9 @@ STRUCTURE_TYPE_IDS, SERVING_FOOD_TYPE_IDS, PIN_TYPE_IDS, - FENCE_ITEM_TYPE_IDS, - MECHANICAL_ITEM_TYPE_IDS, - LINK_ITEM_TYPE_IDS, JSON_EXTRA_ITEM_TYPE_IDS, ROTATABLE_ITEM_TYPE_IDS, enumIndex, enumValue, - itemTypeId, - itemTypeValue, }); })(typeof window !== "undefined" ? window : globalThis); diff --git a/js/save_storage.js b/js/save_storage.js index e82bfdc..45b080f 100644 --- a/js/save_storage.js +++ b/js/save_storage.js @@ -31,5 +31,5 @@ global.localStorage?.removeItem(slotKey(slot)); global.localStorage?.removeItem(metaKey(slot)); } - global.TarinaiSaveStorage = { STORAGE_PREFIX, SLOT_COUNT, writeSlot, readSlot, requireSlot, deleteSlot }; + global.TarinaiSaveStorage = { SLOT_COUNT, writeSlot, readSlot, requireSlot, deleteSlot }; })(typeof window !== "undefined" ? window : globalThis); diff --git a/js/save_system.js b/js/save_system.js index 7968e0b..26fb39f 100644 --- a/js/save_system.js +++ b/js/save_system.js @@ -191,16 +191,5 @@ }); } - global.TarinaiSaveSystem = { - createSnapshot, - restoreSnapshot, - encodeSnapshot, - decodeSnapshot, - saveSlot, - loadSlot, - deleteSlot, - readSlot, - bindSaveSystem, - openDialog, - }; + global.TarinaiSaveSystem = { bindSaveSystem }; })(typeof window !== "undefined" ? window : globalThis); diff --git a/js/sim_core.js b/js/sim_core.js index 13bfc01..af6b179 100644 --- a/js/sim_core.js +++ b/js/sim_core.js @@ -64,33 +64,6 @@ function stableUnit(seed, salt = "") { return ((h >>> 0) % 100000) / 100000; } -function stableKeyPart(value) { - const shared = globalThis.TarinaiDeterministicHelpers?.keyPart; - if (typeof shared === "function") return shared(value); - if (value == null) return "null"; - if (typeof value === "number") return Number.isFinite(value) ? value.toFixed(3) : "nan"; - if (typeof value === "string" || typeof value === "boolean") return String(value); - if (typeof value === "object") { - const id = value.familyKey || value.id || value.seed || value.liveToken || value.name; - if (id) return `${value.type || value.constructor?.name || "entity"}:${id}`; - const x = Number.isFinite(value.x) ? value.x.toFixed(1) : "x"; - const y = Number.isFinite(value.y) ? value.y.toFixed(1) : "y"; - return `${value.type || value.constructor?.name || "entity"}@${x},${y}`; - } - return String(value); -} - -if (typeof window !== "undefined") { - window.TarinaiDeterminism = { - stableKeyPart, - deterministicFrame: window.deterministicFrame, - deterministicUnit: window.deterministicUnit, - deterministicRange: window.deterministicRange, - deterministicSigned: window.deterministicSigned, - deterministicChance: window.deterministicChance, - deterministicAngle: window.deterministicAngle, - }; -} const GENETIC_KEYS = ["lifeSpanMul", "attackMul", "speedMul", "sizeMul"]; const GENETIC_LIMITS = Object.freeze({ @@ -164,14 +137,6 @@ function tarinaiMaxEnergy(tarinai = null) { return Math.round(clamp(100 * tarinaiBodySizeRatio(tarinai), 28, 240)); } -function clampTarinaiEnergy(tarinai = null) { - if (!tarinai) return 100; - const max = tarinaiMaxEnergy(tarinai); - tarinai.maxEnergy = max; - tarinai.energy = clamp(Number(tarinai.energy) || 0, 0, max); - return max; -} - function tarinaiEnergyRatio(tarinai = null) { const max = Math.max(1, typeof tarinaiMaxEnergy === "function" ? tarinaiMaxEnergy(tarinai) : (Number(tarinai?.maxEnergy) || 100)); return clamp((Number(tarinai?.energy) || 0) / max, 0, 1); @@ -185,8 +150,6 @@ function tarinaiMetabolicHungerScale(tarinai = null) { return clamp(attack * speed * size, 0.18, 8.0); } -const PERSONALITIES = Object.freeze({}); -const PERSONALITY_IDS = []; const PERSONALITY_KEYS = ["aggression", "openness", "sociability", "neuroticism"]; const PERSONALITY_DAILY_LIMIT = { perAxis: 0.08, total: 0.20 }; const FRIEND_AFFINITY_THRESHOLD = 14; @@ -235,14 +198,6 @@ function randomBirthPersonality(seed = "") { return out; } -function personalityFromParents(a, b) { - const pa = ensurePersonality(a).currentPersonality; - const pb = ensurePersonality(b).currentPersonality; - const out = {}; - for (const key of PERSONALITY_KEYS) out[key] = clampPersonalityValue(((pa[key] || 0) + (pb[key] || 0)) / 2 + rand(-0.2, 0.2)); - return out; -} - function getTraitStrength(value) { const v = clampPersonalityValue(value); if (v >= 0.75) return { direction: 1, strength: "strong" }; @@ -696,21 +651,6 @@ class SpatialGrid { this.dynamicHazardCells.clear(); } - clearStaticItems(opts = {}) { - this.staticItemCells.clear(); - this.staticObstacleCells.clear(); - this.staticFoodCells.clear(); - this.staticHazardCells.clear(); - if (opts.rebuildCombined) this.rebuildCombinedItemCells(); - } - - clearDynamicItems(opts = {}) { - this.dynamicItemCells.clear(); - this.dynamicObstacleCells.clear(); - this.dynamicFoodCells.clear(); - this.dynamicHazardCells.clear(); - if (opts.rebuildCombined) this.rebuildCombinedItemCells(); - } clearTarinai() { this.tarinaiCells.clear(); @@ -771,7 +711,7 @@ class SpatialGrid { if (trait === "hazard" && it.type === "poison_block") return mech.hazardBoundsAabb?.(it) || mech.boundsAabb?.(it); return mech.boundsAabb?.(it); } - const ropePath = it?.type === "rope" ? ((typeof window !== "undefined" ? window : globalThis).TarinaiPhysicsBodySystem?.particles?.(it) || null) : null; + const ropePath = it?.type === "rope" ? ((typeof window !== "undefined" ? window : globalThis).TarinaiPhysicsBodySystem.particles(it) || null) : null; if (ropePath && ropePath.length) { let left = Infinity, right = -Infinity, top = Infinity, bottom = -Infinity; for (const p of ropePath) { @@ -811,7 +751,7 @@ class SpatialGrid { const velocity = body?.velocity || {}; return motor.powered !== false || Math.abs(Number(velocity.linear || 0)) > 0.05; } - if (type === "poison_block") return Boolean(globalRef.TarinaiMechanicalSystem?.passiveItemAwake?.(it)); + if (type === "poison_block") return Boolean(globalRef.TarinaiMechanicalSystem.passiveItemAwake(it)); if ((it.dropTimer || 0) > 0) return true; if (Math.hypot(it.vx || 0, it.vy || 0) > 0.05) return true; if (it.isStructure && it.type === "plushie" && it.carriedById) return true; @@ -823,11 +763,11 @@ class SpatialGrid { const obstacleMap = dynamic ? this.dynamicObstacleCells : this.staticObstacleCells; const foodMap = dynamic ? this.dynamicFoodCells : this.staticFoodCells; const hazardMap = dynamic ? this.dynamicHazardCells : this.staticHazardCells; - const hasObstacle = typeof itemHasTrait === "function" ? itemHasTrait(type, "obstacle") : (type === "stone" || type === "ball" || type === "nest_box" || type === "bed" || type === "fence_v" || type === "fence_h" || type === "bounce_fence" || type === "bounce_fence_v"); - const hasFood = typeof itemHasTrait === "function" ? itemHasTrait(type, "food_interest") : (type === "sweet" || type === "love_mochi" || type === "fight_mochi" || type === "grass" || type === "water" || type === "ant_corpse" || (typeof isParamEffectItemType === "function" && isParamEffectItemType(type))); - const hasHazard = typeof itemHasTrait === "function" && itemHasTrait(type, "hazard"); + const hasObstacle = itemHasTrait(type, "obstacle"); + const hasFood = itemHasTrait(type, "food_interest"); + const hasHazard = itemHasTrait(type, "hazard"); if (type === "poison_block") { - const body = (typeof window !== "undefined" ? window : globalThis).TarinaiPhysicsBodySystem?.ensureBody?.(it, null, { syncFromLegacy: false }); + const body = (typeof window !== "undefined" ? window : globalThis).TarinaiPhysicsBodySystem.ensureBody(it, null, { syncFromLegacy: false }); const solid = body?.collision ? body.collision.solid !== false : true; if (solid && hasObstacle) this.addTrait(obstacleMap, it, "obstacle"); if (hasHazard) this.addTrait(hazardMap, it, "hazard"); diff --git a/js/simulation.js b/js/simulation.js index 5ba0eae..0d74a0b 100644 --- a/js/simulation.js +++ b/js/simulation.js @@ -5,19 +5,9 @@ (function (global) { function update(worldRef, dt) { const order = global.TarinaiSystemOrder; - if (order?.update) return order.update(worldRef, dt); - const phases = global.TarinaiWorldUpdatePhases; - if (!phases?.update) throw new Error("TarinaiWorldUpdatePhases is not available"); - return phases.update(worldRef, dt); + if (!order?.update) throw new Error("TarinaiSystemOrder is not available"); + return order.update(worldRef, dt); } - function phases() { - return global.TarinaiWorldUpdatePhases?.phases || null; - } - - function systemOrder() { - return global.TarinaiSystemOrder?.names || null; - } - - global.TarinaiSimulation = Object.freeze({ update, phases, systemOrder }); + global.TarinaiSimulation = Object.freeze({ update }); })(typeof window !== "undefined" ? window : globalThis); diff --git a/js/simulation_ambient_system.js b/js/simulation_ambient_system.js index e274498..cfea3bd 100644 --- a/js/simulation_ambient_system.js +++ b/js/simulation_ambient_system.js @@ -15,7 +15,7 @@ function spawnAmbientGrass(worldRef, dt) { if ((worldRef.groundGrassMultiplier?.() ?? 1) <= 0) return; - const groundGrowthMul = Math.max(0, Number(global.TarinaiGround?.grassGrowthMultiplier?.(worldRef.groundType || "soil") ?? (worldRef.groundGrassMultiplier?.() ?? 1)) || 0); + const groundGrowthMul = Math.max(0, Number(global.TarinaiGround.grassGrowthMultiplier(worldRef.groundType || "soil") ?? (worldRef.groundGrassMultiplier?.() ?? 1)) || 0); if (groundGrowthMul <= 0) return; if ((worldRef.canAddGrass?.(1) ?? true) && Math.random() < dt * 0.070 * groundGrowthMul) { const spot = worldRef.findGrassPlantingSpot(rand(60, worldRef.w - 60), rand(60, worldRef.h - 60), { @@ -26,7 +26,7 @@ }); if (spot) { const sprout = new Item("grass", spot.x, spot.y); - global.TarinaiGrass?.setStage?.(sprout, 0, { force: true }); + global.TarinaiGrass.setStage(sprout, 0, { force: true }); if (worldRef.addItem) worldRef.addItem(sprout, "grass-spawn"); else worldRef.items.push(sprout); } @@ -37,7 +37,7 @@ // Rain visuals are diagonal line streaks in render.js. This branch creates // horizontal oval water items spawned by rain; it intentionally does not // share the rain overlay renderer. - if (global.TarinaiWeatherSystem?.shouldDropRainWater?.(worldRef, dt)) { + if (global.TarinaiWeatherSystem.shouldDropRainWater(worldRef, dt)) { const drop = global.TarinaiWeatherSystem.createRainWaterItem(worldRef); worldRef.addItem?.(drop, "rain-water") || worldRef.items.push(drop); worldRef.emit?.("rain:itemDropped", { item: drop, type: "water" }); diff --git a/js/simulation_creature_system.js b/js/simulation_creature_system.js index 7e802b8..f818601 100644 --- a/js/simulation_creature_system.js +++ b/js/simulation_creature_system.js @@ -4,8 +4,8 @@ // Owns Tarinai update cadence, Tarinai movement invalidation, and creature // collision/interactions scheduled after individual updates. (function (global) { - const helpers = () => global.TarinaiSimulationRuntime?.helpers || {}; - const updatePolicy = () => global.TarinaiCreatureUpdatePolicy || {}; + const helpers = global.TarinaiSimulationRuntime.helpers; + const updatePolicy = global.TarinaiCreatureUpdatePolicy; function inc(map, key, n = 1) { const k = String(key || "none"); @@ -13,9 +13,9 @@ } function updateCreatures(worldRef, dt) { - const h = helpers(); - const end = global.TarinaiPerf?.begin?.("update.tarinai.individual") || null; - const visibleRect = h.updateVisibleWorldRect?.(worldRef, 160) || { left: -Infinity, top: -Infinity, right: Infinity, bottom: Infinity }; + const h = helpers; + const end = global.TarinaiPerf.begin("update.tarinai.individual") || null; + const visibleRect = h.updateVisibleWorldRect(worldRef, 160); const now = worldRef.time || 0; const stats = { total: 0, @@ -46,9 +46,9 @@ for (const t of worldRef.tarinai) { if (!t || t.dead) continue; stats.total += 1; - const behaviorId = updatePolicy().behaviorId?.(t) || t.state || "none"; + const behaviorId = updatePolicy.behaviorId(t); inc(stats.states, behaviorId); - const cadence = updatePolicy().updateCadence?.(worldRef, t, visibleRect) || { interval: 0, lane: "legacy", smoothMotion: false, visible: true, urgent: false }; + const cadence = updatePolicy.updateCadence(worldRef, t, visibleRect); const interval = Number.isFinite(cadence.interval) ? cadence.interval : Infinity; inc(stats.lanes, cadence.lane || "unknown"); if (worldRef.selected === t) stats.selected += 1; @@ -66,25 +66,23 @@ t._updateAccum = 0; stats.full += 1; stats.realtime += 1; - if (global.TarinaiCreatureRuntime?.updateOne) global.TarinaiCreatureRuntime.updateOne(t, runDt, { context }); - else t.update(runDt); + global.TarinaiCreatureRuntime.updateOne(t, runDt, { context }); continue; } t._updateAccum = Math.min(2.4, (t._updateAccum || 0) + dt); if (!Number.isFinite(t._nextLowFreqUpdateAt)) { - const jitter = typeof stableUnit === "function" ? stableUnit(t.id || t.familyKey || Math.random(), "creature-cadence") : Math.random(); + const jitter = stableUnit(t.id || t.familyKey || Math.random(), "creature-cadence"); t._nextLowFreqUpdateAt = now + interval * (0.65 + jitter * 0.70); } if (now >= t._nextLowFreqUpdateAt) { const runDt = Math.max(dt, t._updateAccum || dt); - const jitter = typeof stableUnit === "function" ? stableUnit(t.id || t.familyKey || Math.random(), `creature-cadence:${Math.floor(now * 2)}`) : Math.random(); + const jitter = stableUnit(t.id || t.familyKey || Math.random(), `creature-cadence:${Math.floor(now * 2)}`); t._updateAccum = 0; t._nextLowFreqUpdateAt = now + interval * (0.92 + jitter * 0.22); stats.full += 1; - if (global.TarinaiCreatureRuntime?.updateOne) global.TarinaiCreatureRuntime.updateOne(t, runDt, { context }); - else t.update(runDt); - } else if (cadence.smoothMotion && global.TarinaiCreatureRuntime?.updateMotionOnly) { + global.TarinaiCreatureRuntime.updateOne(t, runDt, { context }); + } else if (cadence.smoothMotion) { if (global.TarinaiCreatureRuntime.updateMotionOnly(t, dt, { context })) { stats.smooth += 1; inc(stats.smoothStates, behaviorId); @@ -108,15 +106,15 @@ } else { worldRef.workStats && (worldRef.workStats.bedConflictSkips = (worldRef.workStats.bedConflictSkips || 0) + 1); } - const endSpatial = global.TarinaiPerf?.begin?.("update.tarinai.spatial") || null; - const tarinaiMoved = h.markSpatialDirtyIfMoved?.(worldRef, worldRef.tarinai, "tarinai-moved", 0.25) || false; + const endSpatial = global.TarinaiPerf.begin("update.tarinai.spatial") || null; + const tarinaiMoved = h.markSpatialDirtyIfMoved(worldRef, worldRef.tarinai, "tarinai-moved", 0.25); if (tarinaiMoved || worldRef.spatialDirty) { if (worldRef.rebuildSpatial) worldRef.rebuildSpatial(true, worldRef.deferredSpatialDirtyReason || "post-tarinai-update"); else worldRef.ensureSpatial?.("post-tarinai-update"); worldRef.deferredSpatialDirtyReason = ""; } if (endSpatial) endSpatial(); - const endBallInteractions = global.TarinaiPerf?.begin?.("update.ballInteractions") || null; + const endBallInteractions = global.TarinaiPerf.begin("update.ballInteractions") || null; worldRef.limitBallChasers(); worldRef.resolveBallInteractions(dt); if (endBallInteractions) endBallInteractions(); @@ -128,7 +126,7 @@ if (hasFastTarinai || now >= (worldRef.nextTarinaiCollisionCheckAt || 0)) { worldRef.nextTarinaiCollisionCheckAt = now + collisionInterval; worldRef.workStats && (worldRef.workStats.collisionRuns = (worldRef.workStats.collisionRuns || 0) + 1); - const endCollision = global.TarinaiPerf?.begin?.("update.tarinai.collisions") || null; + const endCollision = global.TarinaiPerf.begin("update.tarinai.collisions") || null; try { worldRef.resolveTarinaiHighSpeedCollisions(dt); } finally { if (endCollision) endCollision(); } } else { @@ -137,11 +135,6 @@ } const phases = Object.freeze({ updateCreatures }); - const helpersApi = Object.freeze({ - updateInterval(worldRef, tarinai, visibleRect) { - return updatePolicy().updateInterval?.(worldRef, tarinai, visibleRect) ?? Infinity; - }, - }); - global.TarinaiCreatureSimulationSystem = Object.freeze({ phases, helpers: helpersApi }); + global.TarinaiCreatureSimulationSystem = Object.freeze({ phases }); })(typeof window !== "undefined" ? window : globalThis); diff --git a/js/simulation_environment_system.js b/js/simulation_environment_system.js index 4cce252..c0b2dcb 100644 --- a/js/simulation_environment_system.js +++ b/js/simulation_environment_system.js @@ -6,24 +6,16 @@ (function (global) { function normalizeDelta(worldRef, dt) { if (!worldRef || worldRef.paused) return null; - let frameDt = dt * worldRef.speed; - frameDt = Math.min(frameDt, 0.12 * worldRef.speed); - return frameDt; + return dt * worldRef.speed; } function updateClock(worldRef, dt) { - const prevTime = worldRef.time; - worldRef.time += dt; const previousDay = worldRef.day || 1; + worldRef.time += dt; worldRef.day = Math.floor(worldRef.time / CONFIG.dayLength) + 1; - const prevHour = worldRef.hourOfDay ? worldRef.hourOfDay(prevTime) : 0; - const nowHour = worldRef.hourOfDay ? worldRef.hourOfDay(worldRef.time) : 0; - const crossedColonyEvalHour = (worldRef.day !== previousDay) ? (nowHour >= 6) : (prevHour < 6 && nowHour >= 6); - const pendingColonyMoodEvaluation = crossedColonyEvalHour && worldRef.lastColonyMoodDay !== worldRef.day; if (worldRef.day !== previousDay) { for (const t of worldRef.tarinai || []) if (t) t.personalityDaily = { day: worldRef.day, total: 0, byKey: {}, byCause: {} }; } - return { prevTime, previousDay, prevHour, nowHour, crossedColonyEvalHour, pendingColonyMoodEvaluation }; } function updateFrameSignals(worldRef, dt) { @@ -44,7 +36,6 @@ function updateEnvironment(worldRef, dt) { worldRef.updateWeather(dt); - worldRef.foodSpoilagePenalty = Math.max(0, (worldRef.foodSpoilagePenalty || 0) - dt * 0.010); worldRef.updateTerrainDirtyState?.(dt); worldRef.evaluateColonyMood?.(); } diff --git a/js/simulation_item_ant_system.js b/js/simulation_item_ant_system.js index 634db89..a051f71 100644 --- a/js/simulation_item_ant_system.js +++ b/js/simulation_item_ant_system.js @@ -4,12 +4,12 @@ // Owns scheduled item updates, random grass growth, ball/ant spatial refresh, // and item/ant spatial post-processing. (function (global) { - const helpers = () => global.TarinaiSimulationRuntime?.helpers || {}; + const helpers = global.TarinaiSimulationRuntime.helpers; function updateRandomGrassGrowth(worldRef, dt) { if (!worldRef || !global.TarinaiGrass) return false; if ((worldRef.groundGrassMultiplier?.() ?? 1) <= 0) return false; - const groundGrowthMul = Math.max(0, Number(global.TarinaiGround?.grassGrowthMultiplier?.(worldRef.groundType || "soil") ?? (worldRef.groundGrassMultiplier?.() ?? 1)) || 0); + const groundGrowthMul = Math.max(0, Number(global.TarinaiGround.grassGrowthMultiplier(worldRef.groundType || "soil") ?? (worldRef.groundGrassMultiplier?.() ?? 1)) || 0); if (groundGrowthMul <= 0) return false; worldRef.grassGrowthTimer = (worldRef.grassGrowthTimer || 0) + dt; const field = worldRef.fieldType || "garden"; @@ -40,7 +40,7 @@ } function updateItemsAndAnts(worldRef, dt) { - const h = helpers(); + const h = helpers; const itemCountBefore = worldRef.items.length; const prevMode = worldRef.deferSpatialRebuildMode || ""; worldRef.deferSpatialRebuildDepth = (worldRef.deferSpatialRebuildDepth || 0) + 1; @@ -48,16 +48,16 @@ let itemsMoved = false; let antsMoved = false; try { - h.updateItemsScheduled?.(worldRef, dt); + h.updateItemsScheduled(worldRef, dt); updateRandomGrassGrowth(worldRef, dt); - itemsMoved = h.markScheduledItemsMoved?.(worldRef, "items-moved", 0.25) || false; + itemsMoved = h.markScheduledItemsMoved(worldRef, "items-moved", 0.25); if (worldRef.itemCounts?.ball) { worldRef.ensureSpatial?.("ball-ball-collisions"); worldRef.resolveBallBallCollisions(dt); worldRef.markSpatialDirty?.("ball-ball-collisions"); } worldRef.updateAnts?.(dt); - antsMoved = h.markSpatialDirtyIfMoved?.(worldRef, worldRef.ants || [], "ants-moved", 0.25) || false; + antsMoved = h.markSpatialDirtyIfMoved(worldRef, worldRef.ants || [], "ants-moved", 0.25); } finally { worldRef.deferSpatialRebuildDepth = Math.max(0, (worldRef.deferSpatialRebuildDepth || 1) - 1); worldRef.deferSpatialRebuildMode = prevMode; @@ -74,7 +74,6 @@ } const phases = Object.freeze({ updateItemsAndAnts }); - const helpersApi = Object.freeze({ updateRandomGrassGrowth }); - global.TarinaiItemAntSimulationSystem = Object.freeze({ phases, helpers: helpersApi }); + global.TarinaiItemAntSimulationSystem = Object.freeze({ phases }); })(typeof window !== "undefined" ? window : globalThis); diff --git a/js/simulation_runtime_helpers.js b/js/simulation_runtime_helpers.js index 141c050..0e658f9 100644 --- a/js/simulation_runtime_helpers.js +++ b/js/simulation_runtime_helpers.js @@ -3,47 +3,19 @@ // Layer: simulation/helpers // Shared, UI-free helpers used by the concrete simulation systems. (function (global) { - const policy = () => global.TarinaiItemUpdatePolicy || {}; + const policy = global.TarinaiItemUpdatePolicy; function forScheduledItems(worldRef, visitor) { - if (policy().forScheduledItems) return policy().forScheduledItems(worldRef, visitor); - if (!worldRef || typeof visitor !== "function") return 0; - let count = 0; - for (const it of worldRef.items || []) { - if (!it || it.dead || it.type === "grass") continue; - visitor(it); - count += 1; - } - return count; + return policy.forScheduledItems(worldRef, visitor); } function itemUpdateInterval(item) { - const interval = policy().itemUpdateInterval?.(item); + const interval = policy.itemUpdateInterval(item); return Number.isFinite(interval) ? interval : Infinity; } function updateItemsScheduled(worldRef, dt) { - if (global.TarinaiItemRuntime?.updateScheduled) return global.TarinaiItemRuntime.updateScheduled(worldRef, dt); - if (global.TarinaiItemUpdateScheduler?.run) return global.TarinaiItemUpdateScheduler.run(worldRef, dt); - let ran = 0; - forScheduledItems(worldRef, (it) => { - const interval = itemUpdateInterval(it); - if (!Number.isFinite(interval)) return; - if (interval <= 0) { - if (global.TarinaiItemRuntime?.updateOne) global.TarinaiItemRuntime.updateOne(it, dt, worldRef); - else it.update?.(dt, worldRef); - ran += 1; - return; - } - it._scheduledUpdateDt = Math.min(10, (it._scheduledUpdateDt || 0) + dt); - if (it._scheduledUpdateDt < interval) return; - const scheduledDt = it._scheduledUpdateDt; - it._scheduledUpdateDt = 0; - if (global.TarinaiItemRuntime?.updateOne) global.TarinaiItemRuntime.updateOne(it, scheduledDt, worldRef); - else it.update?.(scheduledDt, worldRef); - ran += 1; - }); - return ran; + return global.TarinaiItemRuntime.updateScheduled(worldRef, dt); } function markSpatialDirtyIfMoved(worldRef, list, reason = "moved", threshold = 0.35) { @@ -77,7 +49,7 @@ if (!worldRef) return false; const thresholdSq = threshold * threshold; let moved = false; - const tracked = global.TarinaiItemRuntime?.trackedDynamicItems?.(worldRef) || global.TarinaiItemUpdateScheduler?.trackedDynamicItems?.(worldRef); + const tracked = global.TarinaiItemRuntime.trackedDynamicItems(worldRef); const source = tracked && tracked.length ? tracked : null; const visit = (entity) => { const x = Number.isFinite(entity.x) ? entity.x : 0; @@ -120,8 +92,6 @@ } const helpers = Object.freeze({ - forScheduledItems, - itemUpdateInterval, updateItemsScheduled, markSpatialDirtyIfMoved, markScheduledItemsMoved, @@ -130,11 +100,6 @@ }); global.TarinaiSimulationRuntime = Object.freeze({ - helpers, - constants: Object.freeze({ - get REALTIME_ITEM_TYPES() { return policy().REALTIME_ITEM_TYPES || new Set(); }, - get PIN_ITEM_TYPES() { return policy().PIN_ITEM_TYPES || new Set(); }, - get SCHEDULED_ITEM_TYPES() { return policy().SCHEDULED_ITEM_TYPES || Object.freeze([]); }, - }), + helpers }); })(typeof window !== "undefined" ? window : globalThis); diff --git a/js/simulation_systems.js b/js/simulation_systems.js index 79828d3..f25dd55 100644 --- a/js/simulation_systems.js +++ b/js/simulation_systems.js @@ -27,24 +27,6 @@ function finalizeDiagnostics(worldRef) { return phaseFrom("TarinaiEnvironmentSimulationSystem", "finalizeDiagnostics")(worldRef); } // finalizeDiagnostics publishes worldRef.lastRuntimeDiagnostics. - function update(worldRef, dt) { - const frameDt = normalizeDelta(worldRef, dt); - if (frameDt === null) return; - updateClock(worldRef, frameDt); - updateFrameSignals(worldRef, frameDt); - updateEnvironment(worldRef, frameDt); - prepareSpatialFrame(worldRef); - const mobile = updateItemsAndAnts(worldRef, frameDt); - updateEffects(worldRef, frameDt); - updateCreatures(worldRef, frameDt); - runMaintenance(worldRef, frameDt, mobile.itemCountBefore); - emitPhaseChange(worldRef); - spawnAmbientTarinai(worldRef, frameDt); - spawnAmbientGrass(worldRef, frameDt); - spawnRainWater(worldRef, frameDt); - finalizeDiagnostics(worldRef); - } - const phases = Object.freeze({ normalizeDelta, updateClock, @@ -62,15 +44,5 @@ finalizeDiagnostics, }); - const helpers = Object.freeze({ - ...(global.TarinaiSimulationRuntime?.helpers || {}), - ...(global.TarinaiItemAntSimulationSystem?.helpers || {}), - ...(global.TarinaiCreatureSimulationSystem?.helpers || {}), - }); - - global.TarinaiSimulationSystems = Object.freeze({ - update, - phases, - helpers, - }); + global.TarinaiSimulationSystems = Object.freeze({ phases }); })(typeof window !== "undefined" ? window : globalThis); diff --git a/js/snapshot_system.js b/js/snapshot_system.js index ce7b611..4846f88 100644 --- a/js/snapshot_system.js +++ b/js/snapshot_system.js @@ -11,6 +11,8 @@ GROUND_IDS, WEATHER_IDS, MOOD_IDS, + canonicalMoodId, + moodValue, STATE_IDS, POWER_MODE_IDS, SIZE_MODE_IDS, @@ -21,19 +23,6 @@ enumIndex, enumValue, } = SaveSchema; - function spriteIds() { - const list = typeof SPRITES !== "undefined" ? SPRITES : (global.SPRITES || []); - return (list || []).map(s => s?.id).filter(Boolean); - } - function spriteIndex(id = "smile") { - const ids = spriteIds(); - const i = ids.indexOf(String(id || "")); - return i >= 0 ? i : Math.max(0, ids.indexOf("smile")); - } - function spriteValue(index = 0) { - const ids = spriteIds(); - return ids[Number(index) | 0] || "smile"; - } function q(value, scale = 1, fallback = 0) { const n = Number(value); return Number.isFinite(n) ? Math.round(n * scale) : fallback; @@ -78,52 +67,6 @@ } return out; } - function copyOwnData(obj, skip = DEFAULT_SKIP) { - const out = {}; - if (!obj) return out; - for (const key of Object.keys(obj)) { - if (skip?.has?.(key)) continue; - const cloned = clonePlain(obj[key]); - if (cloned !== undefined) out[key] = cloned; - } - return out; - } - function applyOwnData(target, data, skip = DEFAULT_SKIP) { - if (!target || !data) return target; - for (const [key, value] of Object.entries(data)) { - if (skip?.has?.(key) || key.startsWith("__")) continue; - const c = clonePlain(value); - if (c !== undefined) target[key] = c; - } - return target; - } - function refFor(worldRef, value) { - if (!value || typeof value !== "object") return null; - if ((worldRef?.tarinai || []).includes(value)) return { kind: "tarinai", id: value.id || "", familyKey: value.familyKey || "" }; - if ((worldRef?.items || []).includes(value)) return { kind: "item", id: value.id || "", type: value.type || "" }; - if (Number.isFinite(value.x) && Number.isFinite(value.y)) return { kind: "point", x: value.x, y: value.y, dead: Boolean(value.dead) }; - return null; - } - function resolveRef(worldRef, ref) { - if (!ref || typeof ref !== "object") return null; - if (ref.kind === "tarinai") return (worldRef.tarinai || []).find(t => t && !t.dead && ((ref.id && t.id === ref.id) || (ref.familyKey && t.familyKey === ref.familyKey))) || null; - if (ref.kind === "item") return (worldRef.items || []).find(it => it && !it.dead && it.id === ref.id) || null; - if (ref.kind === "point") return { x: Number(ref.x) || 0, y: Number(ref.y) || 0, dead: Boolean(ref.dead) }; - return null; - } - function worldData(worldRef) { - return { - fieldType: worldRef?.fieldType || "garden", - groundType: worldRef?.groundType || "soil", - time: Number(worldRef?.time) || 0, - day: Number(worldRef?.day) || 1, - weather: worldRef?.weather || "sunny", - deadCount: Number(worldRef?.deadCount) || 0, - lastBirthAt: Number(worldRef?.lastBirthAt) || -999, - maxGeneration: Number(worldRef?.maxGeneration) || 1, - colonyMood: worldRef?.colonyMood?.id || worldRef?.colonyMood || "relaxed", - }; - } function needsToArray(needs = {}) { return NEED_KEYS.map(k => q(needs[k], 1)); } @@ -185,13 +128,13 @@ return [ enumIndex(FIELD_IDS, worldRef?.fieldType || "garden"), enumIndex(GROUND_IDS, worldRef?.groundType || "soil"), - enumIndex(MOOD_IDS, worldRef?.colonyMood?.id || worldRef?.colonyMood || "relaxed"), + enumIndex(MOOD_IDS, canonicalMoodId?.(worldRef?.colonyMood?.id || worldRef?.colonyMood || "relaxed") || "relaxed"), worldTick10, enumIndex(WEATHER_IDS, worldRef?.weather || "sunny"), q(worldRef?.lastBirthAt, 10, -9990), q(worldRef?.maxGeneration, 1, 1), q(worldRef?.deadCount, 1, 0), - worldRef?.worldSeed || global.TarinaiSeedFactory?.ensureWorldSeed?.(worldRef) || "", + worldRef?.worldSeed || global.TarinaiSeedFactory.ensureWorldSeed(worldRef) || "", q(worldRef?.birthSerial, 1, 0), ]; } @@ -201,7 +144,7 @@ const totalTime = u(arr[3], 10, 0); worldRef.fieldType = enumValue(FIELD_IDS, arr[0], "garden"); worldRef.weather = enumValue(WEATHER_IDS, arr[4], "sunny"); - worldRef.worldSeed = String(arr[8] || "") || global.TarinaiSeedFactory?.createWorldSeed?.() || `w${Date.now().toString(36)}`; + worldRef.worldSeed = String(arr[8] || "") || global.TarinaiSeedFactory.createWorldSeed() || `w${Date.now().toString(36)}`; worldRef.birthSerial = Math.max(0, u(arr[9], 1, 0)); worldRef.day = Math.max(1, Math.floor(totalTime / dayLength) + 1); worldRef.time = totalTime - (worldRef.day - 1) * dayLength; @@ -211,7 +154,7 @@ worldRef.liveIdSerial = 1; worldRef.lastBirthAt = u(arr[5], 10, -999); worldRef.maxGeneration = Math.max(1, u(arr[6], 1, 1)); - const moodId = enumValue(MOOD_IDS, arr[2], "relaxed"); + const moodId = moodValue?.(arr[2], "relaxed") || enumValue(MOOD_IDS, arr[2], "relaxed"); worldRef.colonyMood = worldRef.colonyMoodDefinition?.(moodId) || { id: moodId, label: moodId, effects: { personality: {} } }; worldRef.setGroundType?.(enumValue(GROUND_IDS, arr[1], "soil"), { silent: true, force: true }); } @@ -276,11 +219,11 @@ const needs = arrayToNeeds(row[9]); const age = u(row[4], 10); const birthSeed = String(row[1] || `s${index}`); - const profile = global.TarinaiSeedFactory?.birthProfile?.(birthSeed) || {}; + const profile = global.TarinaiSeedFactory.birthProfile(birthSeed) || {}; const opts = { familyKey: birthSeed, birthSeed, - birthSerial: global.TarinaiSeedFactory?.serialFromBirthSeed?.(birthSeed) || 0, + birthSerial: global.TarinaiSeedFactory.serialFromBirthSeed(birthSeed) || 0, id: `st${index}`, liveToken: 1, currentPersonality: arrayToPersonality(row[10]), @@ -331,11 +274,11 @@ if (type === "grass") return [q(item.growth, 100), q(item.health, 100), q(item.fertilityBoost, 100), item.manualGrass || item.placedByPlayer ? 1 : 0]; if (type === "zunchi") return [enumIndex(STAGE_IDS, item.stage || "fresh"), q(item.freshness, 100), q(item.fertility, 100)]; if (type === "signboard") return [item.text || ""]; - if (global.TarinaiPhysicsBodySystem?.isPhysicsType?.(type)) { - const packed = global.TarinaiPhysicsBodySystem.compactItemExtra?.(item, tarinaiIndex, itemIndex); + if (global.TarinaiPhysicsBodySystem.isPhysicsType(type)) { + const packed = global.TarinaiPhysicsBodySystem.compactItemExtra(item, tarinaiIndex, itemIndex); if (packed) return packed; } - if (global.TarinaiPhysicsBodySystem?.isPhysicsType?.(type)) return { pf: 2, missing: true }; + if (global.TarinaiPhysicsBodySystem.isPhysicsType(type)) return { pf: 2, missing: true }; if (type === "duplicator") return [enumIndex(ITEM_TYPE_IDS, item.storedFoodType || "", -1)]; if (type === "ball") return [q(item.vx, 10), q(item.vy, 10)]; if (type === "ant_nest") return [q(item.antCount, 1), q(item.queenSpawnAt, 10)]; @@ -376,17 +319,17 @@ } if (type === "grass") { item.growth = u(extra[0], 100, item.growth || 0); item.health = u(extra[1], 100, item.health || 1); item.fertilityBoost = u(extra[2], 100, 0); item.manualGrass = !!extra[3]; item.placedByPlayer = !!extra[3]; - global.TarinaiGrass?.normalize?.(item); + global.TarinaiGrass.normalize(item); } else if (type === "zunchi") { item.stage = enumValue(STAGE_IDS, extra[0], "fresh"); item.freshness = u(extra[1], 100, item.freshness || 1); item.fertility = u(extra[2], 100, item.fertility || 1); } else if (type === "signboard") { item.text = String(extra[0] || ""); - } else if (global.TarinaiPhysicsBodySystem?.applyCompactExtra?.(item, extra, tarinaiList, itemList)) { + } else if (global.TarinaiPhysicsBodySystem.applyCompactExtra(item, extra, tarinaiList, itemList)) { return; - } else if (global.TarinaiPhysicsBodySystem?.isPhysicsType?.(type)) { + } else if (global.TarinaiPhysicsBodySystem.isPhysicsType(type)) { // Physics items only accept the normalized pf:2 payload from this schema. // Old array payloads are intentionally unsupported. - global.TarinaiPhysicsBodySystem?.invalidateItem?.(item, "physics-extra-missing"); + global.TarinaiPhysicsBodySystem.invalidateItem(item, "physics-extra-missing"); return; } else if (type === "duplicator") { item.storedFoodType = enumValue(ITEM_TYPE_IDS, extra[0], ""); item.storedFoodLabel = global.foodLabel ? global.foodLabel(item.storedFoodType) : item.storedFoodType; if (item.roles) item.roles.food = Boolean(item.storedFoodType); @@ -451,7 +394,6 @@ worldRef.selected = null; worldRef.events = global.TarinaiEvents || null; worldRef.liveTarinai = new Map(); - worldRef.liveIdFree = []; applyCompactWorld(worldRef, snapshot.w || []); const tarRows = Array.isArray(snapshot.t) ? snapshot.t : []; @@ -546,12 +488,6 @@ global.TarinaiSnapshot = { version: SNAPSHOT_VERSION, - clonePlain, - copyOwnData, - applyOwnData, - refFor, - resolveRef, - worldData, createSnapshot, restoreSnapshot, }; diff --git a/js/sound_pack.js b/js/sound_pack.js index b3e6a62..c2137d7 100644 --- a/js/sound_pack.js +++ b/js/sound_pack.js @@ -7,7 +7,6 @@ categoryGain: { voice: 3.2, notify: 18.0, ops: 16.0 }, idGain: { sfx_notify: 2.60, - sfx_log_push: 2.60, sfx_ui_click: 2.35, sfx_ui_fold: 2.20, sfx_place: 2.10, @@ -35,7 +34,6 @@ sfx_zunchi: 0.14, sfx_ant_die: 0.14, sfx_notify: 0.10, - sfx_log_push: 0.10, voice_sunbath: 0.95, sfx_shoot: 0.045, sfx_bolt_action: 0.10, @@ -71,9 +69,9 @@ sfx_eat: "voice", sfx_sleep: "voice", sfx_wake: "voice", sfx_birth: "voice", sfx_death: "voice", sfx_damage_soft: "voice", sfx_damage_heavy: "voice", sfx_fight_hit: "voice", sfx_fight_finish: "voice", sfx_disease: "voice", sfx_heal: "voice", sfx_ant_drag: "voice", sfx_ant_die: "voice", sfx_queen_ant: "voice", - sfx_notify: "notify", sfx_phase: "notify", sfx_log_push: "notify", + sfx_notify: "notify", sfx_phase: "notify", sfx_ui_click: "ops", sfx_ui_fold: "ops", sfx_place: "ops", sfx_delete: "ops", sfx_poke: "ops", - sfx_grab: "ops", sfx_drop: "ops", sfx_water_hose: "ops", sfx_water_clean: "ops", sfx_drink: "ops", + sfx_grab: "ops", sfx_drop: "ops", sfx_water_hose: "ops", sfx_drink: "ops", sfx_ball_hit: "ops", sfx_stone_impact: "ops", sfx_firecracker_fuse: "ops", sfx_firecracker_explode: "ops", sfx_shoot: "ops", sfx_bolt_action: "ops", sfx_genkotsu_fall: "ops", sfx_genkotsu_impact: "ops", sfx_ground_rumble: "ops", sfx_grass: "ops", diff --git a/js/structure_lifecycle.js b/js/structure_lifecycle.js index 836d153..9364741 100644 --- a/js/structure_lifecycle.js +++ b/js/structure_lifecycle.js @@ -119,7 +119,6 @@ if (nestMatch) { t.insideNestBoxId = null; - t.nestBoxSleepTimer = 0; } if (sleepTargetMatch && t.sleepSession) { t.sleepSession.targetId = null; @@ -172,15 +171,9 @@ } const api = Object.freeze({ - markWorldAfterStructureChange, - markLooseItemGone, - releaseStructureUsers, - deleteStructure, consumeGrassBedOnWake, deleteItem, - destroyItem: deleteItem, triggerOwnedStructureDestroyedPanic, }); global.TarinaiStructureLifecycle = api; - global.TarinaiItemDestruction = api; })(typeof window !== "undefined" ? window : globalThis); diff --git a/js/structures.js b/js/structures.js index f7e6b43..1c0f9ea 100644 --- a/js/structures.js +++ b/js/structures.js @@ -98,7 +98,7 @@ this.dead = true; this.amount = 0; global.StructureRegistry?.get?.(this.type)?.onDestroyed?.(this, worldRef, breaker); - global.TarinaiStructureLifecycle?.triggerOwnedStructureDestroyedPanic?.(worldRef, this, breaker, "structure-damaged-destroyed"); + global.TarinaiStructureLifecycle.triggerOwnedStructureDestroyedPanic(worldRef, this, breaker, "structure-damaged-destroyed"); return true; } @@ -145,10 +145,10 @@ } else if (this.type === "plushie") { const spriteId = "plushie_bear"; this.plushieSpriteId = spriteId; - const img = typeof getRenderableImage === "function" ? getRenderableImage(spriteId, spriteId) : (global.images?.get?.(spriteId) || null); + const img = getRenderableImage(spriteId, spriteId); ctx.rotate(Math.sin(time * 2 + this.seed) * 0.05); if (img) { - const metrics = typeof getImageMetrics === "function" ? getImageMetrics(spriteId) || getImageMetrics("smile") : null; + const metrics = getImageMetrics(spriteId) || getImageMetrics("smile"); const w = this.r * 3.35; const h = w * (metrics?.ratio || 1.05); ctx.drawImage(img, -w * 0.50, -h * 0.62, w, h); @@ -220,7 +220,6 @@ onUse(user, structure, worldRef, options = {}) { if (user?.id && !structure.ownerId) { structure.ownerId = user.id; - structure.ownerName = user.name || ""; structure.attachment = Math.max(structure.attachment || 0, 24); structure.unownedDecayTimer = 0; structure.dead = false; @@ -244,7 +243,6 @@ structure.unownedDecayTimer = 0; structure.singleUsePending = true; structure.singleUseConsumedById = user?.id || ""; - structure.singleUseConsumedAt = worldRef?.time || 0; structure.singleUseWakeReason = "sleep"; structure.amount = Math.max(0.001, structure.hp || structure.amount || 1); worldRef?.markItemBucketsDirty?.("grass-bed-sleep-reserved"); diff --git a/js/system_order.js b/js/system_order.js index e25ec75..d70699f 100644 --- a/js/system_order.js +++ b/js/system_order.js @@ -5,7 +5,7 @@ // with the phase facade kept as a stable named entry point. (function (global) { function phase(name) { - return global.TarinaiSimulationSystems?.phases?.[name] || global.TarinaiWorldUpdatePhases?.phases?.[name] || null; + return global.TarinaiSimulationSystems.phases?.[name] || null; } const SYSTEM_ORDER = Object.freeze([ @@ -26,13 +26,13 @@ function update(worldRef, dt) { const normalize = phase("normalizeDelta"); - if (!normalize) return global.TarinaiWorldUpdatePhases?.update?.(worldRef, dt); + if (!normalize) throw new Error("Tarinai simulation normalize phase is not available"); const frameDt = normalize(worldRef, dt); if (frameDt === null) return; const ctx = { dt: frameDt, mobile: null }; const profiler = global.TarinaiPerf; for (const system of SYSTEM_ORDER) { - const end = profiler?.begin?.(`update.phase.${system.id}`) || null; + const end = profiler.begin(`update.phase.${system.id}`) || null; try { system.run(worldRef, ctx); } finally { @@ -41,9 +41,5 @@ } } - global.TarinaiSystemOrder = Object.freeze({ - SYSTEM_ORDER, - names: Object.freeze(SYSTEM_ORDER.map(system => system.id)), - update, - }); + global.TarinaiSystemOrder = Object.freeze({ update }); })(typeof window !== "undefined" ? window : globalThis); diff --git a/js/tarinai.js b/js/tarinai.js index ad3c6e3..0626b0d 100644 --- a/js/tarinai.js +++ b/js/tarinai.js @@ -16,18 +16,17 @@ function tarinaiRoundRectPath(ctx, x, y, w, h, r) { } class Tarinai { constructor(world, opts = {}) { - if (world && !opts.birthSeed && !opts.familyKey && !opts.id && globalThis.TarinaiSeedFactory?.prepareAddOptions) { + if (world && !opts.birthSeed && !opts.familyKey && !opts.id && globalThis.TarinaiSeedFactory.prepareAddOptions) { opts = globalThis.TarinaiSeedFactory.prepareAddOptions(world, opts) || opts; } this.world = world; - this.birthSeed = opts.birthSeed || opts.familyKey || opts.id || (globalThis.TarinaiSeedFactory?.childSeed?.(world?.worldSeed || "", Number(world?.birthSerial || 0) + 1, []) || (crypto.randomUUID ? crypto.randomUUID() : `${Date.now()}-${Math.random()}`)); - this.birthSerial = Number.isFinite(opts.birthSerial) ? Number(opts.birthSerial) : (globalThis.TarinaiSeedFactory?.serialFromBirthSeed?.(this.birthSeed) || 0); - const birthProfile = globalThis.TarinaiSeedFactory?.birthProfile?.(this.birthSeed, opts) || {}; + this.birthSeed = opts.birthSeed || opts.familyKey || opts.id || (globalThis.TarinaiSeedFactory.childSeed(world?.worldSeed || "", Number(world?.birthSerial || 0) + 1, []) || (crypto.randomUUID ? crypto.randomUUID() : `${Date.now()}-${Math.random()}`)); + this.birthSerial = Number.isFinite(opts.birthSerial) ? Number(opts.birthSerial) : (globalThis.TarinaiSeedFactory.serialFromBirthSeed(this.birthSeed) || 0); + const birthProfile = globalThis.TarinaiSeedFactory.birthProfile(this.birthSeed, opts) || {}; this.familyKey = opts.familyKey || this.birthSeed; this.id = opts.id || ""; this.liveToken = opts.liveToken || 0; this.name = opts.name || birthProfile.name || makeName(world); - this.personality = ""; this.type = opts.type || birthProfile.type || baseSpriteId(); const typeMeta = SPRITES.find(s => s.id === this.type); if (typeMeta?.actionOnly || typeMeta?.displayOnly) this.type = birthProfile.type || baseSpriteId(); @@ -73,8 +72,6 @@ class Tarinai { constructor(world, opts = {}) { this.needRaw = opts.needRaw && typeof opts.needRaw === "object" ? { ...defaultNeeds, ...opts.needRaw } : { ...this.needs }; this.needRawBase = opts.needRawBase && typeof opts.needRawBase === "object" ? { ...defaultNeeds, ...opts.needRawBase } : { ...this.needRaw }; this.needSatisfaction = opts.needSatisfaction && typeof opts.needSatisfaction === "object" ? { ...defaultNeeds, ...opts.needSatisfaction } : { ...defaultNeeds }; - this.lastSatisfiedAt = opts.lastSatisfiedAt && typeof opts.lastSatisfiedAt === "object" ? { ...opts.lastSatisfiedAt } : {}; - this.lastSatisfactionSource = opts.lastSatisfactionSource || ""; this.stress = typeof calculateStressFromNeeds === "function" ? calculateStressFromNeeds(this.needRaw || this.needs) : 0; this.previousNeeds = opts.previousNeeds && typeof opts.previousNeeds === "object" ? { ...defaultNeeds, ...opts.previousNeeds } : { ...this.needs }; this.need = ""; @@ -92,13 +89,11 @@ class Tarinai { constructor(world, opts = {}) { this.stressBarTimer = opts.stressBarTimer ?? 0; this.affection = opts.affection ?? rand(0, 30); this.dead = !!opts.dead; - this.lifeStatus = opts.lifeStatus || (this.dead ? "dead" : "alive"); this.deathReason = opts.deathReason || ""; this.lastDamageCause = opts.lastDamageCause || ""; this.lastDamageAmount = opts.lastDamageAmount ?? 0; this.lastDamageAt = opts.lastDamageAt ?? -999; this.lastMajorDamageCause = opts.lastMajorDamageCause || ""; - this.lastMajorDamageAmount = opts.lastMajorDamageAmount ?? 0; this.lastMajorDamageAt = opts.lastMajorDamageAt ?? -999; this.thought = ""; this.target = null; @@ -116,16 +111,12 @@ class Tarinai { constructor(world, opts = {}) { this.lastLog = 0; this.sleeping = false; this.awakeLockTimer = opts.awakeLockTimer ?? 0; - this.wasSleepingLastTick = false; - this.blink = rand(0, 10); - this.lastSocial = 0; this.goodMode = opts.goodMode ?? birthProfile.goodMode ?? stableChoice(this.birthSeed || this.familyKey, "good-mode", displayNormalSprites()) ?? "smile"; this.facing = opts.facing ?? (stableUnit(this.familyKey, "facing") < 0.5 ? -1 : 1); this.visualFacing = opts.visualFacing ?? this.facing; this.facingLockUntil = opts.facingLockUntil ?? 0; this.eatTimer = opts.eatTimer ?? 0; this.eatCooldown = opts.eatCooldown ?? 0; - this.lastAteAt = Number.isFinite(opts.lastAteAt) ? opts.lastAteAt : (this.world?.time || 0); this.mealCooldownUntil = Number.isFinite(opts.mealCooldownUntil) ? opts.mealCooldownUntil : 0; this.foodReactTimer = opts.foodReactTimer ?? 0; this.surpriseTimer = opts.surpriseTimer ?? 0; @@ -169,7 +160,6 @@ class Tarinai { constructor(world, opts = {}) { this.favorite = Boolean(opts.favorite); this.insideNestBoxId = opts.insideNestBoxId || null; this.nestFade = opts.nestFade ?? (this.insideNestBoxId ? 1 : 0); - this.nestBoxEnteredAt = opts.nestBoxEnteredAt ?? (this.insideNestBoxId ? (world?.time || 0) : -Infinity); this.nestBoxStayUntil = opts.nestBoxStayUntil ?? (this.insideNestBoxId ? ((world?.time || 0) + 10) : -Infinity); this.focusPulseTimer = opts.focusPulseTimer ?? 0; if (this.isZunchiSlave) { @@ -195,7 +185,6 @@ class Tarinai { constructor(world, opts = {}) { : 0; this.poopCount = opts.poopCount ?? 0; this.oshiriByoZunchiStock = opts.oshiriByoZunchiStock ?? 0; - this.stretchTimer = opts.stretchTimer ?? 0; this.lastMealColor = opts.lastMealColor || "#f7cf67"; this.grassEatTimer = opts.grassEatTimer ?? 0; this.hurtTimer = opts.hurtTimer ?? 0; @@ -210,12 +199,9 @@ class Tarinai { constructor(world, opts = {}) { this.blastSpinTimer = opts.blastSpinTimer ?? 0; this.blastSpinMax = opts.blastSpinMax ?? 0; this.postBirthPeaceTimer = opts.postBirthPeaceTimer ?? 0; - this.sleepStart = 0.60 + stableUnit(this.familyKey, "sleep-start") * 0.22; - this.sleepEnd = 0.15 + stableUnit(this.familyKey, "sleep-end") * 0.14; this.sleepFacing = stableUnit(this.familyKey, "sleep-facing") < 0.5 ? -1 : 1; this.nextBubbleAt = 0; this.nextSleepBubbleAt = 0; - this.nextFearBubbleAt = 0; this.nextCursorHeartAt = opts.nextCursorHeartAt ?? 0; this.cursorPetting = opts.cursorPetting ?? 0; this.aiTimer = opts.aiTimer ?? stableUnit(this.familyKey, "ai-start") * 0.22; diff --git a/js/tarinai_action_planner_system.js b/js/tarinai_action_planner_system.js deleted file mode 100644 index 246223d..0000000 --- a/js/tarinai_action_planner_system.js +++ /dev/null @@ -1,21 +0,0 @@ -"use strict"; - -// Boundary for ordinary need/action planning. The current implementation still -// delegates to the extracted planner body, and callers no longer depend on the -// Tarinai prototype owning that body. - -(function (global) { - function updateOne(tarinai, dt = 0) { - if (!tarinai || tarinai.dead) return false; - return Boolean(global.TarinaiNeedPlannerCoreSystem?.updateOne?.(tarinai, dt)); - } - - function updateWithThis(dt = 0) { - return updateOne(this, dt); - } - - global.TarinaiActionPlannerSystem = Object.freeze({ - updateOne, - updateWithThis, - }); -})(typeof window !== "undefined" ? window : globalThis); diff --git a/js/tarinai_action_spec.js b/js/tarinai_action_spec.js index 98d604e..c648ca4 100644 --- a/js/tarinai_action_spec.js +++ b/js/tarinai_action_spec.js @@ -116,25 +116,12 @@ return specs; } - function registerTarinaiActionSpec(definition = {}) { - const spec = normalizeTarinaiActionSpec(definition); - registry.set(spec.id, spec); - return spec; - } function getTarinaiActionSpec(id = "") { return registry.get(String(id || "")) || null; } - function listTarinaiActionSpecs() { - return Array.from(registry.values()); - } - function selectTarinaiActionTarget(action, tarinai, world, ctx = {}) { - const spec = resolveActionSpec(action); - if (!spec) return null; - return hasOwnFn(spec, "selectTarget") ? spec.selectTarget(tarinai, world, ctx) : (ctx.target ?? tarinai?.target ?? null); - } function canStartTarinaiAction(action, tarinai, world, ctx = {}) { const spec = resolveActionSpec(action); @@ -175,19 +162,13 @@ } Object.assign(global, { - normalizeTarinaiActionSpec, - createTarinaiActionSpec: normalizeTarinaiActionSpec, - registerTarinaiActionSpec, registerTarinaiActionSpecs, getTarinaiActionSpec, - listTarinaiActionSpecs, - selectTarinaiActionTarget, canStartTarinaiAction, startTarinaiAction, tickTarinaiAction, finishTarinaiAction, failTarinaiAction, textTarinaiAction, - TarinaiActionSpecRegistry: registry, }); })(typeof window !== "undefined" ? window : globalThis); diff --git a/js/tarinai_action_state.js b/js/tarinai_action_state.js index b8ef8a3..520fed5 100644 --- a/js/tarinai_action_state.js +++ b/js/tarinai_action_state.js @@ -26,8 +26,7 @@ const reason = String(opts.reason ?? this.thought ?? ""); if (nextState === "idle") { - if (typeof clearBehavior === "function") clearBehavior(this, reason || "\u5f85\u3063\u3066\u3044\u308b"); - else if (typeof clearTarinaiBehavior === "function") clearTarinaiBehavior(this, { clearTarget: !!opts.clearTarget }); + clearBehavior(this, reason || "\u5f85\u3063\u3066\u3044\u308b"); return true; } @@ -52,8 +51,7 @@ const actionId = String(opts.actionId || opts.behaviorId || stateActionIds[nextState] || nextState); const target = opts.clearTarget ? null : ("target" in opts ? opts.target : this.target); const phase = opts.phase || (nextState === "sleep" || nextState === "eat" || nextState === "fight" || nextState === "intimidate" || nextState === "birth_ritual" || nextState === "sunbath" ? "acting" : "approaching"); - if (typeof setBehaviorText === "function") { - setBehaviorText(this, { + setBehaviorText(this, { need: opts.need || null, subNeed: opts.subNeed || "", actionId, @@ -67,19 +65,6 @@ sourceReasonText: opts.sourceReasonText || opts.forcedReasonText || "", causeText: opts.causeText || "", }); - } else if (typeof patchTarinaiBehavior === "function") { - patchTarinaiBehavior(this, { - actionId, - phase, - target, - source: opts.source || "state", - reason, - label: opts.actionLabel || reason || nextState, - text: reason || opts.actionLabel || nextState, - need: opts.need || "fulfill", - subNeed: opts.subNeed || "", - }); - } return true; }, @@ -90,17 +75,17 @@ reason: text, sleeping: false, }); - if (typeof setBehaviorText === "function") setBehaviorText(this, { need: "food", actionId: "eat_food", actionLabel: "\u98df\u3079\u3066\u3044\u308b", reasonText: text, target: item || null, phase: "perform", source: "behavior" }); + setBehaviorText(this, { need: "food", actionId: "eat_food", actionLabel: "\u98df\u3079\u3066\u3044\u308b", reasonText: text, target: item || null, phase: "perform", source: "behavior" }); return ok; }, currentLodgedPin() { - return typeof lodgedPinFor === "function" ? lodgedPinFor(this, this.world) : null; + return lodgedPinFor(this, this.world); }, currentLodgedPinBehavior() { const pin = this.currentLodgedPin ? this.currentLodgedPin() : null; - return typeof pinBehaviorFor === "function" ? pinBehaviorFor(pin?.type) : null; + return pinBehaviorFor(pin?.type); }, hasLodgedPinEffect(effectKey = "") { @@ -133,7 +118,7 @@ target.singleUseWakeReason = "sleep"; } const text = reason || (target?.type === "nest_box" ? "\u5de3\u7bb1\u306e\u4e2d\u3067\u7720\u3063\u3066\u3044\u308b" : "\u7720\u3063\u3066\u3044\u308b"); - if (typeof setBehaviorText === "function") setBehaviorText(this, { need: "sleep", actionId: target ? "sleep_in_bed" : "sleep_anywhere", actionLabel: "\u7720\u3063\u3066\u3044\u308b", reasonText: text, target, phase: "perform", source: "behavior" }); + 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 }); }, @@ -165,7 +150,7 @@ return false; } this.pendingGrassBedWakeId = ""; - return window.TarinaiStructureLifecycle?.consumeGrassBedOnWake?.(worldRef, bed, this, reason) || false; + return window.TarinaiStructureLifecycle.consumeGrassBedOnWake(worldRef, bed, this, reason) || false; }, seekTarget(state, target = null, reason = "") { @@ -173,13 +158,11 @@ }, forceBehavior(actionId = "", opts = {}) { - if (typeof queueForcedTarinaiBehavior !== "function") return false; return queueForcedTarinaiBehavior(this, actionId, opts); }, currentBehaviorText() { - if (typeof behaviorText === "function") return behaviorText(this); - return String((typeof getTarinaiBehaviorText === "function" ? getTarinaiBehaviorText(this) : "") || this.thought || "").trim(); + return behaviorText(this); }, enterPanic(opts = {}) { @@ -210,10 +193,8 @@ wake: opts.wake !== false, sleeping: false, }); - if (typeof setBehaviorText === "function") { - const causeText = this.lastPanicDetail || (String(panicReason).includes("\u3001") ? String(panicReason).split("\u3001")[0].replace(/\u3066$/, "\u305f") : "\u6016\u3044"); - setBehaviorText(this, { need: "safety", actionId: "panic_escape", actionLabel: "\u30d1\u30cb\u30c3\u30af\u306b\u306a\u3063\u3066\u3044\u308b", reasonText: panicReason, causeText, target, phase: "perform", source: "behavior" }); - } + const causeText = this.lastPanicDetail || (String(panicReason).includes("\u3001") ? String(panicReason).split("\u3001")[0].replace(/\u3066$/, "\u305f") : "\u6016\u3044"); + setBehaviorText(this, { need: "safety", actionId: "panic_escape", actionLabel: "\u30d1\u30cb\u30c3\u30af\u306b\u306a\u3063\u3066\u3044\u308b", reasonText: panicReason, causeText, target, phase: "perform", source: "behavior" }); this.fearTimer = Math.max(this.fearTimer || 0, 2.4); } if (Number.isFinite(opts.surpriseTimer)) this.surpriseTimer = Math.max(this.surpriseTimer || 0, opts.surpriseTimer); diff --git a/js/tarinai_behavior_state.js b/js/tarinai_behavior_state.js index 61f34e1..5df47c3 100644 --- a/js/tarinai_behavior_state.js +++ b/js/tarinai_behavior_state.js @@ -229,8 +229,6 @@ Object.assign(global, { normalizeTarinaiBehaviorState: normalizeBehaviorState, - normalizeTarinaiBehaviorValue: normalizeBehaviorValue, - getTarinaiBehavior, currentTarinaiBehavior: getTarinaiBehavior, getTarinaiBehaviorId, getTarinaiBehaviorNeed, @@ -245,6 +243,5 @@ clearTarinaiForcedBehavior: function clearTarinaiForcedBehavior(tarinai) { return setTarinaiForcedBehavior(tarinai, null); }, isTarinaiBehaviorForced: function isTarinaiBehaviorForced(tarinai) { return isBehaviorForcedValue(ensureBehavior(tarinai)); }, isTarinaiBehaviorValueForced: isBehaviorForcedValue, - tarinaiBehaviorForcedSource: forcedBehaviorSource, }); })(typeof window !== "undefined" ? window : globalThis); diff --git a/js/tarinai_behavior_text.js b/js/tarinai_behavior_text.js index 28e0f4e..20c2576 100644 --- a/js/tarinai_behavior_text.js +++ b/js/tarinai_behavior_text.js @@ -1,7 +1,7 @@ "use strict"; function fulfillPrimaryReason(tarinai, action = null) { - const behavior = typeof currentTarinaiBehavior === "function" ? currentTarinaiBehavior(tarinai) : tarinai?.behavior; + const behavior = currentTarinaiBehavior(tarinai); const actionId = action?.id || behavior?.actionId || ""; if (actionId === "build_grass_bed") return "\u5bdd\u5e8a\u304c\u307b\u3057\u3044"; if (actionId === "build_plushie") return "\u306c\u3044\u3050\u308b\u307f\u304c\u307b\u3057\u3044"; @@ -34,7 +34,7 @@ function needPhraseForReason(need, tarinai = null, action = null) { } function actionTextForReason(action = null, tarinai = null) { - const behavior = typeof currentTarinaiBehavior === "function" ? currentTarinaiBehavior(tarinai) : tarinai?.behavior; + const behavior = currentTarinaiBehavior(tarinai); const id = action?.id || behavior?.actionId || ""; const target = tarinai?.target || currentBehaviorTarget?.(tarinai) || null; const name = target?.name || "\u76f8\u624b"; @@ -75,7 +75,7 @@ function formatCauseActionText(cause = "", actionText = "\u884c\u52d5\u3057\u306 function causePhraseForReason(chosenNeed, action, tarinai = null, options = {}) { if (options?.causeText) return String(options.causeText || "").replace(/[\u3002\uff01\uff1f]+$/, ""); - const b = (typeof currentTarinaiBehavior === "function" ? currentTarinaiBehavior(tarinai) : tarinai?.behavior) || {}; + const b = (currentTarinaiBehavior(tarinai)) || {}; if (b.causeText) { const cause = String(b.causeText).replace(/[\u3002\uff01\uff1f]+$/, ""); const forced = (typeof isTarinaiBehaviorValueForced === "function" ? isTarinaiBehaviorValueForced(b) : Boolean(b.forcedId || b.forcedRequest)) || (typeof isTarinaiBehaviorForced === "function" ? isTarinaiBehaviorForced(tarinai) : false); @@ -139,7 +139,7 @@ function causeForBehaviorText(tarinai, behavior = {}, action = null, options = { } function composeTarinaiBehaviorText(tarinai, behavior = null, options = {}) { - const b = behavior || (typeof currentTarinaiBehavior === "function" ? currentTarinaiBehavior(tarinai) : tarinai?.behavior) || null; + const b = behavior || (currentTarinaiBehavior(tarinai)) || null; if (!b) return ""; const stored = String(b.text || b.reason || "").trim(); if (stored && options.regenerate !== true) return stored; @@ -148,7 +148,7 @@ function composeTarinaiBehaviorText(tarinai, behavior = null, options = {}) { const tiedNeeds = Array.isArray(options.tiedNeeds) && options.tiedNeeds.length ? [...options.tiedNeeds] : needsForBehaviorText(tarinai, b, need); const causeText = causeForBehaviorText(tarinai, { ...b, need }, action, options); const generated = action ? buildReasonText(need, tiedNeeds, action, tarinai, tarinai?.world || null, { ...options, causeText }) : ""; - const actionLabel = typeof textTarinaiAction === "function" ? textTarinaiAction(action || b.actionId, tarinai, tarinai?.world || null, { ...options, behavior: b, need, tiedNeeds, causeText, phase: options.phase || b.phase || "active" }) : ""; + const actionLabel = textTarinaiAction(action || b.actionId, tarinai, tarinai?.world || null, { ...options, behavior: b, need, tiedNeeds, causeText, phase: options.phase || b.phase || "active" }); const rawFallback = String(actionLabel || b.label || b.reason || b.text || "").trim(); const catalog = globalThis.TEXT_CATALOG || (typeof window !== "undefined" ? window.TEXT_CATALOG : null); const fallback = catalog?.cleanBehaviorText @@ -158,22 +158,21 @@ function composeTarinaiBehaviorText(tarinai, behavior = null, options = {}) { } function patchBehaviorTextFromComposer(tarinai, behavior = null, options = {}) { - const b = behavior || (typeof currentTarinaiBehavior === "function" ? currentTarinaiBehavior(tarinai) : tarinai?.behavior) || null; + const b = behavior || (currentTarinaiBehavior(tarinai)) || null; if (!b) return ""; const prevReason = String(b.reason || ""); const text = composeTarinaiBehaviorText(tarinai, b, { ...options, regenerate: true }); if (!text) return ""; const tiedNeeds = Array.isArray(options.tiedNeeds) && options.tiedNeeds.length ? [...options.tiedNeeds] : needsForBehaviorText(tarinai, b, b.need || options.need || "fulfill"); if (text !== b.reason || text !== b.text || JSON.stringify(tiedNeeds) !== JSON.stringify(b.tiedNeeds || [])) { - if (typeof patchTarinaiBehavior === "function") patchTarinaiBehavior(tarinai, { reason: text, text: text, tiedNeeds }); - else { b.reason = text; b.text = text; b.tiedNeeds = tiedNeeds; } + patchTarinaiBehavior(tarinai, { reason: text, text: text, tiedNeeds }); } if (text && (!tarinai.thought || tarinai.thought === prevReason || tarinai.thought === b.label)) tarinai.thought = text; return text; } function refreshBehaviorReasonText(tarinai, action = null) { - const behavior = typeof currentTarinaiBehavior === "function" ? currentTarinaiBehavior(tarinai) : tarinai?.behavior; + const behavior = currentTarinaiBehavior(tarinai); if (!behavior?.need) return ""; return patchBehaviorTextFromComposer(tarinai, behavior, { action }); } diff --git a/js/tarinai_building_behavior.js b/js/tarinai_building_behavior.js index 97c38bd..fe2a1a1 100644 --- a/js/tarinai_building_behavior.js +++ b/js/tarinai_building_behavior.js @@ -32,7 +32,7 @@ function stopFailedBuildPlan(t, world, type = "", reason = "") { } t.behaviorSwitchCooldownUntil = Math.max(t.behaviorSwitchCooldownUntil || 0, (world?.time || 0) + 1.2); const text = reason || "\u4f5c\u308b\u5834\u6240\u304c\u898b\u3064\u304b\u3089\u306a\u3044"; - if (typeof clearBehavior === "function") clearBehavior(t, text); + clearBehavior(t, text); t.goIdle?.(text); return false; } @@ -54,7 +54,6 @@ function forceBuiltStructureVisualRefresh(world, structure, reason = "structure- world.ensureSpatial?.(reason); if (structure) { structure.world = world; - structure._builtVisibleAt = Number(world.time || 0) || 0; } if (typeof globalThis.render === "function") { globalThis.render(); @@ -146,7 +145,7 @@ function findNearbyBuildMaterial(world, tarinai, type, maxDist = 520) { if (d > maxDist) continue; // Prefer nearby grass, but lightly spread simultaneous builders across // separate tufts so a crowd does not all reserve one valid material. - const jitter = typeof stableUnit === "function" ? stableUnit(tarinai.id || tarinai.familyKey || "", `build-material:${item.id || ""}`) : Math.random(); + const jitter = stableUnit(tarinai.id || tarinai.familyKey || "", `build-material:${item.id || ""}`); const score = d + Math.max(0, reserved) * 42 + jitter * 12; if (score < bestScore) { best = item; bestScore = score; } } diff --git a/js/tarinai_consumable_behavior.js b/js/tarinai_consumable_behavior.js index d3811cf..71da60f 100644 --- a/js/tarinai_consumable_behavior.js +++ b/js/tarinai_consumable_behavior.js @@ -73,10 +73,10 @@ function consumeBehaviorTarget(t, world, target, role = "food", dt = 0.12) { return true; } - if ((foodType === "sweet" || foodType === "food" || (isDuplicatorFoodSource(target) && foodType === "grass") || (typeof isServingFoodType === "function" && isServingFoodType(foodType)) || (typeof isParamEffectItemType === "function" && isParamEffectItemType(foodType))) && t.foodItemHasServingLeft?.(target)) { + if ((foodType === "sweet" || foodType === "food" || (isDuplicatorFoodSource(target) && foodType === "grass") || (isServingFoodType(foodType)) || (isParamEffectItemType(foodType))) && t.foodItemHasServingLeft?.(target)) { const eating = t.consumeFoodServing?.(target); if (!(eating > 0)) return false; - const isParam = typeof isParamEffectItemType === "function" && isParamEffectItemType(foodType); + const isParam = isParamEffectItemType(foodType); const isSpecial = foodType === "love_mochi" || foodType === "fight_mochi" || foodType === "sleep_drug" || isParam; const color = ({ sweet: "#78b957", love_mochi: "#ff7bab", fight_mochi: "#e07c43", sleep_drug: "#b89cff", protein: "#f28d3d", niteropu: "#6255a8", ammo: "#d0942f", laxative: "#788c3b", mercury: "#6e95a8", giant_drug: "#e47b3a", dwarf_drug: "#7773c8", zunda_juice: "#61b94f" })[foodType] || "#f1dfb7"; finishBehaviorConsumption(t, target, foodType, eating, effectiveDt, `${labelFor(foodType)}\u3092\u98df\u3079\u3066\u3044\u308b`, color); @@ -100,7 +100,7 @@ function consumeBehaviorTarget(t, world, target, role = "food", dt = 0.12) { } if (target.type === "grass" && (target.amount || 0) > 0) { - const bite = window.TarinaiGrass?.regress?.(target, 1) ?? 0; + const bite = window.TarinaiGrass.regress(target, 1) ?? 0; if (!(bite > 0)) return false; target.eatenAmount = (target.eatenAmount || 0) + bite; world.markTerrainDirty?.("grass-eaten-stage"); diff --git a/js/tarinai_contact_item_system.js b/js/tarinai_contact_item_system.js index 722be2c..7662ace 100644 --- a/js/tarinai_contact_item_system.js +++ b/js/tarinai_contact_item_system.js @@ -73,14 +73,8 @@ if (d > contactReach) return false; return true; } - function updateOne(tarinai, ctx, dt = 0) { - if (!tarinai || tarinai.dead || !ctx) return false; - for (const it of ctx.foodCandidates || []) updateCandidate(tarinai, ctx, it, dt); - return true; - } global.TarinaiContactItemSystem = Object.freeze({ updateCandidate, - updateOne, }); })(typeof window !== "undefined" ? window : globalThis); diff --git a/js/tarinai_cursor_care_system.js b/js/tarinai_cursor_care_system.js index 66299cd..f0513d7 100644 --- a/js/tarinai_cursor_care_system.js +++ b/js/tarinai_cursor_care_system.js @@ -34,7 +34,6 @@ } global.TarinaiCursorCareSystem = Object.freeze({ - updateBody, updateOne, }); })(typeof window !== "undefined" ? window : globalThis); diff --git a/js/tarinai_disease_nest.js b/js/tarinai_disease_nest.js index 78928b4..8321a3a 100644 --- a/js/tarinai_disease_nest.js +++ b/js/tarinai_disease_nest.js @@ -259,7 +259,6 @@ this._nestBoxSeekStartedAt = NaN; this._nestBoxSeekTargetId = ""; const entry = this.world?.nestBoxEntryPoint ? this.world.nestBoxEntryPoint(box) : { x: box.x, y: box.y }; - this.nestBoxEnteredAt = now; this.nestBoxStayUntil = Math.max(this.nestBoxStayUntil || -Infinity, now + 14); this.nestBoxSlotIndex = Math.min(occupants.length, Math.max(0, capacity - 1)); this._nestBoxEntryStartX = Number.isFinite(this.x) ? this.x : entry.x; @@ -394,7 +393,6 @@ this.sleeping = false; this.sleepSession = null; if (this.state === "sleep" || this.state === "seek_bed") this.goIdle?.("巣箱から出た"); - this.nestBoxEnteredAt = -Infinity; this.nestBoxStayUntil = -Infinity; this.nestBoxSlotIndex = -1; this._nestBoxEntrySmoothUntil = -Infinity; @@ -451,7 +449,7 @@ this._nestBoxSeekStartedAt = this.world?.time || 0; this._nestBoxSeekTargetId = targetBox.id; } - if (!this.insideNestBoxId && global.TarinaiNestSleepSystem?.sleepPlaceAvailableFor && !global.TarinaiNestSleepSystem.sleepPlaceAvailableFor(this.world, this, targetBox)) { + if (!this.insideNestBoxId && global.TarinaiNestSleepSystem.sleepPlaceAvailableFor && !global.TarinaiNestSleepSystem.sleepPlaceAvailableFor(this.world, this, targetBox)) { return this.abortNestBoxEntry?.(targetBox, "巣箱がいっぱいで入れない") || null; } diff --git a/js/tarinai_food_interaction_system.js b/js/tarinai_food_interaction_system.js index c47fb45..bdcf575 100644 --- a/js/tarinai_food_interaction_system.js +++ b/js/tarinai_food_interaction_system.js @@ -11,7 +11,7 @@ const d = it?.type === "duplicator" ? foodInteractionDistance(tarinai, it) : dis const contactReach = foodInteractionReach(tarinai, it, "food") + (it === tarinai.target && foodActionActive ? (it?.type === "duplicator" ? 18 : 4) : 0); if (d > contactReach) return false; const foodType = it.type === "duplicator" ? (it.storedFoodType || "") : it.type; -const duplicatorConsumable = it.type === "duplicator" && foodType && (foodType === "sweet" ? canSweetBite : (canBite || foodActionActive)) && !(typeof isParamEffectItemType === "function" && isParamEffectItemType(foodType)) && foodType !== "sleep_drug" && foodType !== "love_mochi" && foodType !== "fight_mochi"; +const duplicatorConsumable = it.type === "duplicator" && foodType && (foodType === "sweet" ? canSweetBite : (canBite || foodActionActive)) && !(isParamEffectItemType(foodType)) && foodType !== "sleep_drug" && foodType !== "love_mochi" && foodType !== "fight_mochi"; if (((foodType === "sweet" && canSweetBite) || (foodType === "food" && (canBite || foodActionActive)) || duplicatorConsumable) && tarinai.foodItemHasServingLeft(it)) { const eating = tarinai.consumeFoodServing(it); @@ -51,7 +51,7 @@ if (((foodType === "sweet" && canSweetBite) || (foodType === "food" && (canBite } } -if (canSweetBite && (foodType === "love_mochi" || foodType === "fight_mochi" || foodType === "sleep_drug" || (typeof isParamEffectItemType === "function" && isParamEffectItemType(foodType))) && tarinai.foodItemHasServingLeft(it)) { +if (canSweetBite && (foodType === "love_mochi" || foodType === "fight_mochi" || foodType === "sleep_drug" || (isParamEffectItemType(foodType))) && tarinai.foodItemHasServingLeft(it)) { const eating = tarinai.consumeFoodServing(it); if (eating <= 0) return false; const eatLabel = it.type === "duplicator" ? (it.storedFoodLabel || toolLabel(foodType || it.type)) : toolLabel(it.type); @@ -179,16 +179,8 @@ if (it.type === "zunchi") { return false; } - function updateOne(tarinai, ctx, dt = 0) { - if (!tarinai || tarinai.dead || !ctx) return false; - for (const it of ctx.foodCandidates || []) { - if (updateCandidate(tarinai, ctx, it, dt)) return true; - } - return false; - } global.TarinaiFoodInteractionSystem = Object.freeze({ updateCandidate, - updateOne, }); })(typeof window !== "undefined" ? window : globalThis); diff --git a/js/tarinai_forced_action_planner_system.js b/js/tarinai_forced_action_planner_system.js deleted file mode 100644 index 22aa534..0000000 --- a/js/tarinai_forced_action_planner_system.js +++ /dev/null @@ -1,22 +0,0 @@ -"use strict"; - -// Boundary for forced-action planning. The concrete forced-queue implementation -// remains in tarinai_forced_behavior.js / tarinai_needs_items.js; this module -// gives the need planner a stable dependency point. - -(function (global) { - function hasPending(tarinai, actionId = "") { - if (!tarinai) return false; - if (typeof tarinai.hasForcedBehavior === "function") return tarinai.hasForcedBehavior(actionId); - return false; - } - - function prepare(tarinai, dt = 0) { - return Boolean(tarinai && !tarinai.dead && hasPending(tarinai)); - } - - global.TarinaiForcedActionPlannerSystem = Object.freeze({ - hasPending, - prepare, - }); -})(typeof window !== "undefined" ? window : globalThis); diff --git a/js/tarinai_forced_behavior.js b/js/tarinai_forced_behavior.js index 9886fae..698ca0b 100644 --- a/js/tarinai_forced_behavior.js +++ b/js/tarinai_forced_behavior.js @@ -15,7 +15,7 @@ function behaviorTargetId(target = null) { function currentBehaviorTarget(tarinai, behavior = null) { if (!tarinai) return null; - const b = behavior || (typeof currentTarinaiBehavior === "function" ? currentTarinaiBehavior(tarinai) : tarinai.behavior) || null; + const b = behavior || (currentTarinaiBehavior(tarinai)) || null; if (!b) return tarinai.target || null; const ref = b.target || null; const id = ref?.id || b.targetId || null; @@ -128,12 +128,12 @@ function behaviorIsForced(behavior = null) { } function isCurrentBehaviorForced(tarinai) { - return behaviorIsForced(typeof currentTarinaiBehavior === "function" ? currentTarinaiBehavior(tarinai) : tarinai?.behavior); + return behaviorIsForced(currentTarinaiBehavior(tarinai)); } function currentForcedBehaviorRequest(tarinai) { if (typeof getTarinaiForcedBehavior === "function") return getTarinaiForcedBehavior(tarinai); - const b = typeof currentTarinaiBehavior === "function" ? currentTarinaiBehavior(tarinai) : tarinai?.behavior; + const b = currentTarinaiBehavior(tarinai); return behaviorIsForced(b) ? (b.forcedRequest || { uid: b.forcedId || "", id: b.actionId || "", source: b.data?.forcedSource || b.source || "external", priority: Number(b.priority || 0) || 0 }) : null; } @@ -149,7 +149,7 @@ function clearForcedBehaviorQueue(tarinai, predicate = null) { } function behaviorText(tarinai) { - const b = typeof currentTarinaiBehavior === "function" ? currentTarinaiBehavior(tarinai) : tarinai?.behavior; + const b = currentTarinaiBehavior(tarinai); if (!b) return ""; const state = String(tarinai.state || ""); const timerActive = (tarinai.birthRitualTimer || 0) > 0.04 || (tarinai.fightTimer || 0) > 0.04 || (tarinai.eatTimer || 0) > 0.04 || (tarinai.intimidateTimer || 0) > 0.04 || (tarinai.fearTimer || 0) > 0.04; @@ -162,7 +162,7 @@ function behaviorText(tarinai) { function setBehavior(tarinai, fields = {}) { if (!tarinai || tarinai.dead) return null; const now = tarinai.world?.time || 0; - const previous = (typeof currentTarinaiBehavior === "function" ? currentTarinaiBehavior(tarinai) : tarinai.behavior) || {}; + const previous = (currentTarinaiBehavior(tarinai)) || {}; const previousActionId = previous.actionId || ""; const actionId = fields.actionId || fields.id || previousActionId || tarinai.state || "idle"; const action = fields.action || actionById(actionId) || null; @@ -241,8 +241,7 @@ function setBehaviorFromAction(tarinai, action, choice = {}, reasonText = "", op function clearBehavior(tarinai, reason = "") { if (!tarinai) return; - if (typeof clearTarinaiBehavior === "function") clearTarinaiBehavior(tarinai, { reason }); - else tarinai.behavior = null; + clearTarinaiBehavior(tarinai, { reason }); if (reason) tarinai.thought = reason; } @@ -262,7 +261,7 @@ function queueForcedTarinaiBehavior(tarinai, actionId, opts = {}) { function completeForcedBehavior(tarinai, behavior = null, status = "completed") { if (!tarinai) return false; - const b = behavior || (typeof currentTarinaiBehavior === "function" ? currentTarinaiBehavior(tarinai) : tarinai.behavior) || {}; + const b = behavior || (currentTarinaiBehavior(tarinai)) || {}; const forcedId = b.forcedId || ""; if (!forcedId) return false; const queue = forcedBehaviorQueueOf(tarinai); @@ -275,7 +274,7 @@ function completeForcedBehavior(tarinai, behavior = null, status = "completed") function retryForcedBehavior(tarinai, behavior = null, reason = "retry") { if (!tarinai) return false; - const b = behavior || (typeof currentTarinaiBehavior === "function" ? currentTarinaiBehavior(tarinai) : tarinai.behavior) || {}; + const b = behavior || (currentTarinaiBehavior(tarinai)) || {}; const forcedId = b.forcedId || ""; if (!forcedId) return false; const now = tarinai.world?.time || 0; @@ -300,7 +299,7 @@ function processForcedBehaviorQueue(tarinai, world, needs) { } // \u5b9f\u884c\u4e2d\u306e\u5f37\u5236\u884c\u52d5\u306f\u30ad\u30e5\u30fc\u306b\u6b8b\u3057\u3001\u5b8c\u4e86/\u5931\u6557/\u671f\u9650\u5207\u308c\u307e\u3067\u52b9\u679c\u3092\u4fdd\u6301\u3059\u308b\u3002 - const activeQueueBehavior = typeof currentTarinaiBehavior === "function" ? currentTarinaiBehavior(tarinai) : tarinai.behavior; + const activeQueueBehavior = currentTarinaiBehavior(tarinai); const activeForcedId = activeQueueBehavior?.forcedId || ""; for (const entry of queue) { if (!entry.uid) entry.uid = `${entry.id}:${entry.source || "external"}:${entry.createdAt || now}:${Math.random().toString(36).slice(2, 8)}`; @@ -386,7 +385,7 @@ function effectCauseTextLooksStale(text = "") { function setBehaviorText(tarinai, { need = null, actionId = null, actionLabel = null, reasonText = null, reason = null, target = undefined, subNeed = "", phase = "active", source = "state", forced = null, forcedId = "", sourceReasonText = "", forcedReasonText = "", causeText = "" } = {}) { if (!tarinai) return; const now = tarinai.world?.time || 0; - const previousBehavior = (typeof currentTarinaiBehavior === "function" ? currentTarinaiBehavior(tarinai) : tarinai.behavior) || {}; + const previousBehavior = (currentTarinaiBehavior(tarinai)) || {}; const previousActionId = previousBehavior.actionId || ""; const nextNeed = need || previousBehavior.need || "social"; const nextActionId = actionId || previousActionId || tarinai.state || "idle"; @@ -467,6 +466,6 @@ function synchronizeActionTextFromState(tarinai) { return; } if (state === "sleep" || tarinai.sleeping) { - setBehaviorText(tarinai, { need: "sleep", actionId: (typeof getTarinaiBehaviorId === "function" ? getTarinaiBehaviorId(tarinai) : tarinai.behavior?.actionId) || "sleep_anywhere", actionLabel: "\u7720\u3063\u3066\u3044\u308b", reason: catalogText || "\u7720\u3063\u3066\u3044\u308b" }); + setBehaviorText(tarinai, { need: "sleep", actionId: (getTarinaiBehaviorId(tarinai)) || "sleep_anywhere", actionLabel: "\u7720\u3063\u3066\u3044\u308b", reason: catalogText || "\u7720\u3063\u3066\u3044\u308b" }); } } diff --git a/js/tarinai_identity_social.js b/js/tarinai_identity_social.js index 51182ea..9823f8e 100644 --- a/js/tarinai_identity_social.js +++ b/js/tarinai_identity_social.js @@ -135,12 +135,6 @@ shouldApplyPersonalityBehavior(key, direction = 1) { return shouldApplyPersonalityBehavior(this, key, direction); }, - fightWinRate() { - const wins = Number(this.totalFightWins || 0) || 0; - const losses = Number(this.totalFightLosses || 0) || 0; - const total = wins + losses; - return total > 0 ? wins / total : 0; - }, tarinaiChampionEligible() { const wins = Number(this.totalFightWins || 0) || 0; diff --git a/js/tarinai_item_effects.js b/js/tarinai_item_effects.js index 63bab22..97fcaaf 100644 --- a/js/tarinai_item_effects.js +++ b/js/tarinai_item_effects.js @@ -4,16 +4,18 @@ const Tarinai = global.Tarinai; if (!Tarinai) throw new Error("Tarinai is not available for mixin: tarinai_item_effects.js"); - const EFFECT_REGISTRY = global.TarinaiItemRegistry?.effect || null; - const ITEM_EFFECT_LABELS = Object.freeze(EFFECT_REGISTRY?.labels?.() || {}); - const ITEM_EFFECT_COLORS = Object.freeze(EFFECT_REGISTRY?.colors?.() || {}); + const EFFECT_REGISTRY = global.TarinaiItemRegistry.effect || null; const RANDOM_EFFECT_POOL = Object.freeze(EFFECT_REGISTRY?.randomPool?.() || [ "love_mochi", "fight_mochi", "sleep_drug", "laxative", "ammo", "protein", "niteropu", "mercury", "giant_drug", "dwarf_drug", "disease_zunchi", "disease_sleep", "disease_explosion", "disease_fight", ]); function effectLabel(id = "") { - return EFFECT_REGISTRY?.label?.(id) || ITEM_EFFECT_LABELS[id] || id || "\u4e0d\u660e"; + return EFFECT_REGISTRY?.label?.(id) || id || "\u4e0d\u660e"; + } + + function effectColor(id = "", fallback = "rgba(255,242,160,0.85)") { + return EFFECT_REGISTRY?.color?.(id) || fallback; } function sizeDurationScale(item = null) { @@ -184,7 +186,7 @@ const picks = pickRandomEffects(2); for (const effect of picks) this.applyRandomizedBehaviorEffect(effect, item); this.mysteryDrugHistory = [...(this.mysteryDrugHistory || []), `${picks.map(effectLabel).join(" + ")}`].slice(-8); - this.bubble("???", 1.2, ITEM_EFFECT_COLORS.mystery_drug); + this.bubble("???", 1.2, effectColor("mystery_drug")); this.spawnPowerItemEffect("mystery_drug", 1.0); applied = picks; } else if (EFFECT_REGISTRY?.apply?.(type, this, { item, scale, source: options.source || "item", nutrition: options.nutrition })) { @@ -237,11 +239,10 @@ const raw = Math.max(0, Number(amount) || 0); const actual = this.hasItemEffect("laxative") ? raw * (EFFECT_REGISTRY?.modifier?.("laxative", "hungerReliefMultiplier", 0) ?? 0) : raw; const now = this.world?.time || 0; - if (actual > 0.2) this.lastAteAt = now; if (actual >= 3.5) { const day = Number(this.world?.config?.dayLength || CONFIG?.dayLength || 120) || 120; const base = day / 3.65; - const jitter = typeof stableUnit === "function" ? (0.82 + stableUnit(this.familyKey || this.id, "meal-cooldown") * 0.36) : 1; + const jitter = 0.82 + stableUnit(this.familyKey || this.id, "meal-cooldown") * 0.36; this.mealCooldownUntil = Math.max(this.mealCooldownUntil || 0, now + base * jitter); this.needSatisfaction = this.needSatisfaction || (typeof createDefaultNeeds === "function" ? createDefaultNeeds() : {}); this.needSatisfaction.food = Math.max(this.needSatisfaction.food || 0, Math.min(88, actual * 1.85)); @@ -249,7 +250,7 @@ this.actionCooldowns.food = Math.max(this.actionCooldowns.food || 0, 5.2); } this.hunger -= actual; - if (actual >= 3.5 && this.hunger < 68 && (typeof getTarinaiBehaviorNeed === "function" ? getTarinaiBehaviorNeed(this) : this.behavior?.need) === "food" && !(typeof isTarinaiBehaviorForced === "function" ? isTarinaiBehaviorForced(this) : Boolean(this.behavior?.source === "forced" || this.behavior?.forcedId || this.behavior?.forcedRequest))) { + if (actual >= 3.5 && this.hunger < 68 && (getTarinaiBehaviorNeed(this)) === "food" && !(typeof isTarinaiBehaviorForced === "function" ? isTarinaiBehaviorForced(this) : Boolean(this.behavior?.source === "forced" || this.behavior?.forcedId || this.behavior?.forcedRequest))) { this.behaviorLockTimer = Math.min(this.behaviorLockTimer || 0, 0.25); } return actual; @@ -304,7 +305,7 @@ this.digest = Math.max(0, (this.digest || 0) - 3.0); const now = this.world?.time || 0; this.nextPoopBubbleAt = now + 2.2; - this.bubble("\u3076\u308a\u3085\u3063", 1.2, ITEM_EFFECT_COLORS.laxative, { force: true }); + this.bubble("\u3076\u308a\u3085\u3063", 1.2, effectColor("laxative"), { force: true }); if ((this.world.time || 0) > (this.nextLaxativeLogAt || -999)) { this.nextLaxativeLogAt = (this.world.time || 0) + 9; this.world.log?.(`${this.name}\u306f\u4e0b\u5264\u306e\u52b9\u679c\u3067\u305a\u3093\u3061\u3092\u653e\u51fa\u3057\u305f\u3002`, "accident", { participants: [this] }); @@ -315,7 +316,7 @@ if (!this.world?.effects) return; const color = kind === "protein" ? "rgba(224,54,40,0.88)" - : (kind === "niteropu" ? "rgba(116,80,190,0.84)" : (ITEM_EFFECT_COLORS[kind] || "rgba(255,242,160,0.85)")); + : (kind === "niteropu" ? "rgba(116,80,190,0.84)" : effectColor(kind)); const n = Math.max(1, Math.round((kind === "protein" || kind === "niteropu" ? 3 : 2) * intensity)); for (let i = 0; i < n; i++) { const angle = rand(0, Math.PI * 2); @@ -381,11 +382,9 @@ activeItemEffectSummary() { this.ensureItemEffectState(); - if (EFFECT_REGISTRY?.activeSummary) return EFFECT_REGISTRY.activeSummary(this); + if (EFFECT_REGISTRY.activeSummary) return EFFECT_REGISTRY.activeSummary(this); return []; }, })); - global.TARINAI_ITEM_EFFECT_LABELS = ITEM_EFFECT_LABELS; - global.TARINAI_ITEM_EFFECT_COLORS = ITEM_EFFECT_COLORS; })(typeof window !== "undefined" ? window : globalThis); diff --git a/js/tarinai_item_interaction_context.js b/js/tarinai_item_interaction_context.js index 04e9924..1cbdac4 100644 --- a/js/tarinai_item_interaction_context.js +++ b/js/tarinai_item_interaction_context.js @@ -8,7 +8,7 @@ if (!tarinai || !tarinai.world) return null; const foodNeed = Number(tarinai.needRaw?.food ?? tarinai.needs?.food ?? 0) || 0; const healthNeed = Number(tarinai.needRaw?.health ?? tarinai.needs?.health ?? 0) || 0; - const currentNeed = typeof getTarinaiBehaviorNeed === "function" ? getTarinaiBehaviorNeed(tarinai) : tarinai.behavior?.need; + const currentNeed = getTarinaiBehaviorNeed(tarinai); const foodActionActive = currentNeed === "food" || ["seek_food", "eat", "seek_water"].includes(tarinai.state); const healthActionActive = currentNeed === "health" || tarinai.state === "seek_food"; const mealReady = (tarinai.mealCooldownUntil || 0) <= (tarinai.world?.time || 0) || tarinai.hunger > 92 || foodNeed > 84; diff --git a/js/tarinai_item_interaction_system.js b/js/tarinai_item_interaction_system.js index abf5d9b..d63a73e 100644 --- a/js/tarinai_item_interaction_system.js +++ b/js/tarinai_item_interaction_system.js @@ -8,17 +8,17 @@ (function (global) { function interactWithItems(dt = 0) { - const ctx = global.TarinaiItemInteractionContext?.create?.(this, dt); + const ctx = global.TarinaiItemInteractionContext.create(this, dt); if (!ctx) return false; for (const item of ctx.foodCandidates || []) { - const consumed = Boolean(global.TarinaiFoodInteractionSystem?.updateCandidate?.(this, ctx, item, dt)); + const consumed = Boolean(global.TarinaiFoodInteractionSystem.updateCandidate(this, ctx, item, dt)); if (consumed) { - global.TarinaiItemInteractionContext?.finalize?.(this); + global.TarinaiItemInteractionContext.finalize(this); return true; } - global.TarinaiContactItemSystem?.updateCandidate?.(this, ctx, item, dt); + global.TarinaiContactItemSystem.updateCandidate(this, ctx, item, dt); } - global.TarinaiItemInteractionContext?.finalize?.(this); + global.TarinaiItemInteractionContext.finalize(this); return true; } @@ -27,13 +27,8 @@ return interactWithItems.call(tarinai, dt); } - function updateWithThis(dt = 0) { - return updateOne(this, dt); - } global.TarinaiItemInteractionSystem = Object.freeze({ - interactWithItems, updateOne, - updateWithThis, }); })(typeof window !== "undefined" ? window : globalThis); diff --git a/js/tarinai_item_targeting.js b/js/tarinai_item_targeting.js index dc6510c..b7a2608 100644 --- a/js/tarinai_item_targeting.js +++ b/js/tarinai_item_targeting.js @@ -16,16 +16,16 @@ const NORMAL_POOP_MEAL_THRESHOLD = 3; function normalPoopUnitsFor(foodType = "", amount = 0, options = {}) { const type = String(foodType || ""); if (type === "laxative" || options.laxative === true) return 0; - if (typeof isServingFoodType === "function" && isServingFoodType(type)) return 1; + if (isServingFoodType(type)) return 1; const value = Math.max(0, Number(amount) || 0); return value / 18; } function effectiveFoodRoleMatches(item, role) { const type = effectiveFoodTypeForItem(item); if (!type) return false; - if (role === "food") return ["sweet", "food", "grass", "ant_corpse", "zunchi"].includes(type) || (typeof isServingFoodType === "function" && isServingFoodType(type)); + if (role === "food") return ["sweet", "food", "grass", "ant_corpse", "zunchi"].includes(type) || (isServingFoodType(type)); if (role === "drink") return type === "water" || type === "zunda_juice"; - if (role === "medicine") return type === "sweet" || type === "water" || type === "zunda_juice" || type === "sleep_drug" || (typeof isParamEffectItemType === "function" && isParamEffectItemType(type)); + if (role === "medicine") return type === "sweet" || type === "water" || type === "zunda_juice" || type === "sleep_drug" || (isParamEffectItemType(type)); return false; } @@ -62,7 +62,7 @@ function foodInteractionReach(tarinai, item, role = "food") { function foodPriorityRank(tarinai, item) { const type = effectiveFoodTypeForItem(item); if (!type) return -1; - const need = typeof getTarinaiBehaviorNeed === "function" ? getTarinaiBehaviorNeed(tarinai) : tarinai?.behavior?.need; + const need = getTarinaiBehaviorNeed(tarinai); const socialNeed = Number(tarinai?.needRaw?.social ?? tarinai?.needs?.social ?? 0) || 0; const healthNeed = Number(tarinai?.needRaw?.health ?? tarinai?.needs?.health ?? 0) || 0; if (type === "sweet") return 4300 + Math.min(120, Math.max(0, healthNeed - 40) * 3); @@ -71,9 +71,9 @@ function foodPriorityRank(tarinai, item) { if (type === "love_mochi") return 3340 + Math.min(120, Math.max(0, socialNeed - 45) * 3); if (type === "fight_mochi") return 3280 + Math.min(90, Math.max(0, Number(tarinai?.fightMochiTimer || 0)) * 3); if (type === "sleep_drug") return need === "sleep" ? 3380 : 3220; - if (typeof isParamEffectItemType === "function" && isParamEffectItemType(type)) return 3260 + Math.min(120, Math.max(0, healthNeed - 45) * 3); + if (isParamEffectItemType(type)) return 3260 + Math.min(120, Math.max(0, healthNeed - 45) * 3); if (type === "food" || type === "ant_corpse") return 3300; - if (typeof isServingFoodType === "function" && isServingFoodType(type)) return 3300; + if (isServingFoodType(type)) return 3300; if (type === "grass") { const hunger = Number(tarinai?.hunger || 0) || 0; const foodNeed = Number(tarinai?.needRaw?.food ?? tarinai?.needs?.food ?? 0) || 0; @@ -103,7 +103,7 @@ function hasBetterFoodThanZunchiNearby(tarinai, world, maxDist = 180) { if (tarinai.shouldAvoidTarget?.(it)) continue; const type = effectiveFoodTypeForItem(it); if (!type) continue; - if (["sweet", "food", "grass", "water", "zunda_juice", "ant_corpse"].includes(type) || (typeof isServingFoodType === "function" && isServingFoodType(type))) { + if (["sweet", "food", "grass", "water", "zunda_juice", "ant_corpse"].includes(type) || (isServingFoodType(type))) { if (it.type === "grass" && (it.amount || 0) <= 0) continue; if (typeof tarinai.foodItemHasServingLeft === "function" && ["sweet", "food", "zunda_juice"].includes(type) && !tarinai.foodItemHasServingLeft(it)) continue; return true; @@ -153,7 +153,7 @@ function findNearestItemWithRole(world, tarinai, role, maxDist = 520) { if (!roleMatches(item, role)) continue; if (role === "food" && typeof tarinai?.foodItemHasServingLeft === "function") { const type = effectiveFoodTypeForItem(item); - if ((item.type === "duplicator" || (typeof isServingFoodType === "function" && isServingFoodType(type))) && !tarinai.foodItemHasServingLeft(item)) continue; + if ((item.type === "duplicator" || (isServingFoodType(type))) && !tarinai.foodItemHasServingLeft(item)) continue; if (item.type === "grass" && (Number(item.amount || 0) || 0) <= 0) continue; if (item.type === "zunchi" && (Number(item.amount || 0) || 0) <= 0) continue; } @@ -172,19 +172,19 @@ function findNearestItemWithRole(world, tarinai, role, maxDist = 520) { } function sleepPlaceAvailableFor(world, tarinai, item) { - return globalThis.TarinaiNestSleepSystem?.sleepPlaceAvailableFor?.(world, tarinai, item) || false; + return globalThis.TarinaiNestSleepSystem.sleepPlaceAvailableFor(world, tarinai, item) || false; } function nearestSleepPlaceOfType(world, tarinai, type, maxDist = 520) { - return globalThis.TarinaiNestSleepSystem?.nearestSleepPlaceOfType?.(world, tarinai, type, maxDist) || null; + return globalThis.TarinaiNestSleepSystem.nearestSleepPlaceOfType(world, tarinai, type, maxDist) || null; } function findPrioritySleepPlaceBeforeGrassBedBuild(world, tarinai, maxDist = 520) { - return globalThis.TarinaiNestSleepSystem?.findPrioritySleepPlaceBeforeGrassBedBuild?.(world, tarinai, maxDist) || null; + return globalThis.TarinaiNestSleepSystem.findPrioritySleepPlaceBeforeGrassBedBuild(world, tarinai, maxDist) || null; } function findNearestSleepPlace(world, tarinai, maxDist = 520) { - return globalThis.TarinaiNestSleepSystem?.findNearestSleepPlace?.(world, tarinai, maxDist) || null; + return globalThis.TarinaiNestSleepSystem.findNearestSleepPlace(world, tarinai, maxDist) || null; } function findNearbyDanger(world, tarinai, maxDist = 150) { @@ -316,7 +316,7 @@ function findUnownedStructure(world, tarinai, type = "", maxDist = Infinity) { function consumeGrassMaterial(material, stages = 1) { if (!material) return false; const steps = Math.max(1, Math.floor(Number(stages) || 1)); - const consumed = window.TarinaiGrass?.regress?.(material, steps) ?? 0; + const consumed = window.TarinaiGrass.regress(material, steps) ?? 0; if (!(consumed > 0)) return false; material.eatenAmount = (material.eatenAmount || 0) + consumed; material.world?.markTerrainDirty?.("grass-material-consumed"); diff --git a/js/tarinai_local_environment_system.js b/js/tarinai_local_environment_system.js index 0e82ffb..801b57c 100644 --- a/js/tarinai_local_environment_system.js +++ b/js/tarinai_local_environment_system.js @@ -46,7 +46,6 @@ } global.TarinaiLocalEnvironmentSystem = Object.freeze({ - updateBody, updateOne, }); })(typeof window !== "undefined" ? window : globalThis); diff --git a/js/tarinai_need_planner_system.js b/js/tarinai_need_planner_system.js index b514b81..422d580 100644 --- a/js/tarinai_need_planner_system.js +++ b/js/tarinai_need_planner_system.js @@ -1,22 +1,16 @@ "use strict"; // Public Tarinai need-planning entrypoint. This owns the resolveNeeds boundary -// used by Tarinai.prototype.resolveNeeds and by future planner decomposition. +// used by Tarinai.prototype.resolveNeeds. (function (global) { function updateOne(tarinai, dt = 0) { if (!tarinai || tarinai.dead) return false; - global.TarinaiForcedActionPlannerSystem?.prepare?.(tarinai, dt); - return Boolean(global.TarinaiActionPlannerSystem?.updateOne?.(tarinai, dt) - ?? global.TarinaiNeedPlannerCoreSystem?.updateOne?.(tarinai, dt)); + return Boolean(global.TarinaiNeedPlannerCoreSystem.updateOne(tarinai, dt)); } - function updateWithThis(dt = 0) { - return updateOne(this, dt); - } global.TarinaiNeedPlannerSystem = Object.freeze({ updateOne, - updateWithThis, }); })(typeof window !== "undefined" ? window : globalThis); diff --git a/js/tarinai_needs_core.js b/js/tarinai_needs_core.js index 56a5f27..ff9ee24 100644 --- a/js/tarinai_needs_core.js +++ b/js/tarinai_needs_core.js @@ -177,20 +177,16 @@ function applyNeedShock(tarinai, deltas = {}, breaker = null) { return true; } -function applyNeedSatisfaction(tarinai, deltas = {}, source = "") { +function applyNeedSatisfaction(tarinai, deltas = {}) { if (!tarinai || tarinai.dead) return false; tarinai.needSatisfaction = tarinai.needSatisfaction || createDefaultNeeds(); - tarinai.lastSatisfiedAt = tarinai.lastSatisfiedAt || {}; - const now = tarinai.world?.time || 0; let changed = false; for (const key of TARINAI_NEED_KEYS) { const amount = Math.max(0, Number(deltas[key] || 0) || 0); if (amount <= 0) continue; tarinai.needSatisfaction[key] = clamp((tarinai.needSatisfaction[key] || 0) + amount, 0, 88); - tarinai.lastSatisfiedAt[key] = now; changed = true; } - if (source) tarinai.lastSatisfactionSource = source; return changed; } @@ -265,7 +261,7 @@ function needThreshold(need, kind = "start") { function actionLockSeconds(action, tarinai = null) { const base = Number(action?.lockSeconds ?? TARINAI_ACTION_LOCK_SECONDS[action?.id] ?? 2.0) || 2.0; const key = `${tarinai?.familyKey || tarinai?.id || "tarinai"}:${action?.id || "idle"}`; - const jitter = typeof stableUnit === "function" ? (stableUnit(key, "action-lock") - 0.5) * 0.18 : (Math.random() - 0.5) * 0.12; + const jitter = (stableUnit(key, "action-lock") - 0.5) * 0.18; return Math.max(0.5, base * (1 + jitter)); } @@ -278,12 +274,12 @@ function scoreNeedForAction(needs, tarinai, world, need) { const hunger = Number(tarinai?.hunger || 0) || 0; score += 3 + Math.max(0, hunger - 70) * 0.55 + Math.max(0, hunger - 92) * 0.95; } - if ((typeof getTarinaiBehaviorNeed === "function" ? getTarinaiBehaviorNeed(tarinai) : tarinai?.behavior?.need) === need) score += 10; + if ((getTarinaiBehaviorNeed(tarinai)) === need) score += 10; const cooldown = Number(tarinai?.actionCooldowns?.[need] || 0) || 0; if (cooldown > 0) score -= Math.min(22, cooldown * 2.8); const nowBucket = Math.floor((world?.time || 0) / 8); const key = `${tarinai?.familyKey || tarinai?.id || "tarinai"}:${need}:${nowBucket}`; - const noise = typeof stableUnit === "function" ? (stableUnit(key, "need-score") - 0.5) * 3.2 : (Math.random() - 0.5) * 2.0; + const noise = (stableUnit(key, "need-score") - 0.5) * 3.2; return score + noise; } diff --git a/js/tarinai_needs_items.js b/js/tarinai_needs_items.js index e548514..29738e8 100644 --- a/js/tarinai_needs_items.js +++ b/js/tarinai_needs_items.js @@ -133,7 +133,6 @@ function updateNeeds(tarinai, world, dt = 0) { material: materialPart, openness: openness > 0 ? openness * 16 : 0, }; - tarinai.fulfillPrimaryReason = fulfillPrimaryReason(tarinai); for (const key of TARINAI_NEED_KEYS) { const shock = Number(tarinai.needShock?.[key] || 0) || 0; @@ -164,7 +163,7 @@ function updateNeeds(tarinai, world, dt = 0) { } function tarinaiNeedCalcInterval(tarinai, world) { - const jitter = typeof stableUnit === "function" ? stableUnit(tarinai?.familyKey || tarinai?.id || "tarinai", "need-calc-interval") * 0.35 : Math.random() * 0.30; + const jitter = stableUnit(tarinai?.familyKey || tarinai?.id || "tarinai", "need-calc-interval") * 0.35; const urgent = (tarinai?.fightTimer || 0) > 0.04 || (tarinai?.birthRitualTimer || 0) > 0.04 || (tarinai?.eatTimer || 0) > 0.04 || (tarinai?.sunbathTimer || 0) > 0.04 || forcedBehaviorQueueOf(tarinai).length > 0; return urgent ? (0.55 + jitter * 0.25) : (1.0 + jitter); } @@ -186,7 +185,7 @@ function updateNeedsCached(tarinai, world, dt = 0, options = {}) { const now = Number(world?.time || 0) || 0; if (!Number.isFinite(tarinai.nextNeedCalcAt)) { const firstInterval = tarinaiNeedCalcInterval(tarinai, world); - const offset = typeof stableUnit === "function" ? stableUnit(tarinai.familyKey || tarinai.id || "tarinai", "need-calc-offset") * firstInterval : Math.random() * firstInterval; + const offset = stableUnit(tarinai.familyKey || tarinai.id || "tarinai", "need-calc-offset") * firstInterval; tarinai.nextNeedCalcAt = now + offset; } const force = Boolean(options.force || shouldForceNeedRecalc(tarinai, world)); @@ -197,7 +196,6 @@ function updateNeedsCached(tarinai, world, dt = 0, options = {}) { const urgent = (tarinai?.fightTimer || 0) > 0.04 || (tarinai?.birthRitualTimer || 0) > 0.04 || (tarinai?.eatTimer || 0) > 0.04 || (tarinai?.sunbathTimer || 0) > 0.04 || forcedBehaviorQueueOf(tarinai).length > 0; const calcDt = Number(options.fixedNeedDt) || (urgent ? 0.50 : 1.00); const result = updateNeeds(tarinai, world, calcDt); - tarinai.lastNeedCalcAt = now; tarinai.nextNeedCalcAt = now + tarinaiNeedCalcInterval(tarinai, world); return result; } @@ -273,7 +271,7 @@ function shouldStartActionBySubNeed(tarinai, action, needs) { : subNeed === "mate" ? Math.max(0, Number(cur.openness ?? prof.play ?? 0) || 0) * 7 : 0; const threshold = Math.max(6, socialSubNeedStartThreshold(subNeed) - modifier); - const isContinuingSameAction = (typeof getTarinaiBehaviorId === "function" ? getTarinaiBehaviorId(tarinai) : tarinai?.behavior?.actionId) === action.id; + const isContinuingSameAction = (getTarinaiBehaviorId(tarinai)) === action.id; if (subNeed !== "conflict" && subValue >= threshold) return true; if (subNeed !== "conflict" && isContinuingSameAction && subValue >= threshold * 0.55) return true; if (subNeed === "mate") return total >= needThreshold("social", "start") && subValue >= threshold * 0.62 && ((tarinai?.loveMochiTimer || 0) > 0.04 || (tarinai?.reproductionTimer || 0) <= 6 || Number(parts.mate || 0) >= Number(parts.bond || 0) - 2); @@ -372,7 +370,7 @@ function shouldStartNeedAction(tarinai, need, needs) { } if (need === "sleep" && circadianSleepPhase(tarinai?.world).night && value >= Math.max(44, needThreshold("sleep", "start") - 16)) return true; if (value >= needThreshold(need, "start")) return true; - if ((typeof getTarinaiBehaviorNeed === "function" ? getTarinaiBehaviorNeed(tarinai) : tarinai?.behavior?.need) === need && value >= needThreshold(need, "continue")) return true; + if ((getTarinaiBehaviorNeed(tarinai)) === need && value >= needThreshold(need, "continue")) return true; return false; } @@ -385,18 +383,16 @@ function startNeedAction(tarinai, world, choice, action, reasonText, options = { phase: "start", }); tarinai.behaviorLockTimer = Math.max(tarinai.behaviorLockTimer || 0, lockSeconds); - const ran = typeof startTarinaiAction === "function" - ? startTarinaiAction(action, tarinai, world, options) - : action?.run?.(tarinai, world, options); + const ran = startTarinaiAction(action, tarinai, world, options); if (!ran) { - if (typeof failTarinaiAction === "function") failTarinaiAction(action, tarinai, world, { phase: "start_failed" }); - if (isCurrentBehaviorForced(tarinai)) retryForcedBehavior(tarinai, (typeof currentTarinaiBehavior === "function" ? currentTarinaiBehavior(tarinai) : tarinai.behavior), "start_failed"); + failTarinaiAction(action, tarinai, world, { phase: "start_failed" }); + if (isCurrentBehaviorForced(tarinai)) retryForcedBehavior(tarinai, (currentTarinaiBehavior(tarinai)), "start_failed"); tarinai.actionCooldowns = tarinai.actionCooldowns || {}; tarinai.actionIdCooldowns = tarinai.actionIdCooldowns || {}; tarinai.actionCooldowns[action?.need || choice?.need || "fulfill"] = Math.max(tarinai.actionCooldowns[action?.need || choice?.need || "fulfill"] || 0, 1.4); tarinai.actionIdCooldowns[action?.id || "unknown"] = Math.max(tarinai.actionIdCooldowns[action?.id || "unknown"] || 0, 2.2); tarinai.behaviorSwitchCooldownUntil = Math.max(tarinai.behaviorSwitchCooldownUntil || 0, (world?.time || 0) + 0.55); - clearBehavior(tarinai, (typeof getTarinaiBehaviorText === "function" ? getTarinaiBehaviorText(tarinai) : "") || action?.label || "\u5f85\u3063\u3066\u3044\u308b"); + clearBehavior(tarinai, getTarinaiBehaviorText(tarinai) || action?.label || "\u5f85\u3063\u3066\u3044\u308b"); tarinai.goIdle?.(action?.label || "\u5f85\u3063\u3066\u3044\u308b"); } else { const target = tarinai.target || options.target; @@ -405,7 +401,7 @@ function startNeedAction(tarinai, world, choice, action, reasonText, options = { target, state: tarinai.state || action?.state || "idle", lockSeconds, - minDuration: (typeof currentTarinaiBehavior === "function" ? currentTarinaiBehavior(tarinai) : tarinai.behavior)?.minDuration || Math.max(0.45, lockSeconds * 0.72), + minDuration: (currentTarinaiBehavior(tarinai))?.minDuration || Math.max(0.45, lockSeconds * 0.72), phase: "active", }); } @@ -420,7 +416,7 @@ function shouldInterruptCurrentAction(tarinai, needs) { } function continueCurrentBehavior(tarinai, world, needs, dt) { - const behavior = typeof currentTarinaiBehavior === "function" ? currentTarinaiBehavior(tarinai) : tarinai?.behavior; + const behavior = currentTarinaiBehavior(tarinai); const actionId = behavior?.actionId; if (!actionId) return false; if (shouldInterruptCurrentAction(tarinai, needs) && !isCurrentBehaviorForced(tarinai)) return false; @@ -445,11 +441,9 @@ function continueCurrentBehavior(tarinai, world, needs, dt) { if (!foodSuppressed && (elapsed < minDuration || value >= needThreshold(need, "continue") || mustContinueByTimer || isCurrentBehaviorForced(tarinai))) { tarinai.behaviorLockTimer = Math.max(tarinai.behaviorLockTimer || 0, Math.min(0.9, Math.max(0.35, minDuration - elapsed))); const refreshedReason = refreshBehaviorReasonText(tarinai, action); - const ranUpdate = typeof tickTarinaiAction === "function" - ? tickTarinaiAction(action, tarinai, world, dt, needs, { phase: "continue" }) - : action.update?.(tarinai, world, dt, needs); + const ranUpdate = tickTarinaiAction(action, tarinai, world, dt, needs, { phase: "continue" }); if (ranUpdate === "finished") { - if (typeof finishTarinaiAction === "function") finishTarinaiAction(action, tarinai, world, { phase: "finished" }); + finishTarinaiAction(action, tarinai, world, { phase: "finished" }); const finishedBehavior = behavior; if (behaviorIsForced(finishedBehavior)) completeForcedBehavior(tarinai, finishedBehavior, "completed"); tarinai.actionCooldowns = tarinai.actionCooldowns || {}; @@ -458,7 +452,7 @@ function continueCurrentBehavior(tarinai, world, needs, dt) { return false; } if (ranUpdate === false) { - if (typeof failTarinaiAction === "function") failTarinaiAction(action, tarinai, world, { phase: "update_failed" }); + failTarinaiAction(action, tarinai, world, { phase: "update_failed" }); if (behaviorIsForced(behavior)) retryForcedBehavior(tarinai, behavior, "update_failed"); tarinai.actionCooldowns = tarinai.actionCooldowns || {}; tarinai.actionIdCooldowns = tarinai.actionIdCooldowns || {}; @@ -486,8 +480,7 @@ function continueCurrentBehavior(tarinai, world, needs, dt) { tarinai.thought = behaviorText(tarinai) || refreshedReason || behavior?.label || tarinai.thought; const needsRefresh = !tarinai.target || tarinai.target.dead || ["rest_to_recover", "use_plushie", "intimidate_enemy"].includes(action.id); if (needsRefresh && elapsed >= Math.min(0.7, minDuration)) { - if (typeof startTarinaiAction === "function") startTarinaiAction(action, tarinai, world, { phase: "refresh" }); - else action.run?.(tarinai, world); + startTarinaiAction(action, tarinai, world, { phase: "refresh" }); } return true; } @@ -572,7 +565,7 @@ function resolveNeedsCore(dt) { if (continueBuildPlan(this, this.world, dt)) return; const lockedTargetInvalid = this.target && this.target.dead; - const lockedBehavior = typeof currentTarinaiBehavior === "function" ? currentTarinaiBehavior(this) : this.behavior; + const lockedBehavior = currentTarinaiBehavior(this); if ((this.behaviorLockTimer || 0) > 0 && lockedBehavior && !lockedTargetInvalid && !["panic", "intimidate", "fight", "birth_ritual"].includes(this.state)) { if (continueCurrentBehavior(this, this.world, needs, dt)) return; this.thought = lockedBehavior.reason || lockedBehavior.label || this.thought; @@ -589,7 +582,7 @@ function resolveNeedsCore(dt) { if (this.intimidateTimer > 0.04) { const target = this.world.liveTarinaiById?.(this.intimidateTargetId) || null; this.setActionState?.("intimidate", { target, reason: "\u76f8\u624b\u3092\u5a01\u5687\u3057\u3066\u3044\u308b" }); - if (typeof setBehaviorText === "function") setBehaviorText(this, { need: "social", subNeed: "conflict", actionId: "intimidate_enemy", actionLabel: "\u5a01\u5687\u3057\u3066\u3044\u308b", reason: target?.name ? `${target.name}\u3092\u5a01\u5687\u3057\u3066\u3044\u308b` : "\u76f8\u624b\u3092\u5a01\u5687\u3057\u3066\u3044\u308b", target, phase: "perform", source: "behavior" }); + setBehaviorText(this, { need: "social", subNeed: "conflict", actionId: "intimidate_enemy", actionLabel: "\u5a01\u5687\u3057\u3066\u3044\u308b", reason: target?.name ? `${target.name}\u3092\u5a01\u5687\u3057\u3066\u3044\u308b` : "\u76f8\u624b\u3092\u5a01\u5687\u3057\u3066\u3044\u308b", target, phase: "perform", source: "behavior" }); this.bubble("!", 1.2); return; } @@ -613,13 +606,13 @@ function resolveNeedsCore(dt) { this.counterAttackFromId = null; this.fightCooldown = Math.max(this.fightCooldown || 0, CONFIG.fightCooldown + deterministicRange(this.world, "fight-lost-target-cooldown", 4, 8, this, rival)); if (rival) this.world?.markFightPairCooldown?.(this, rival, CONFIG.fightCooldown + 6); - if (typeof clearForcedBehaviorQueue === "function") clearForcedBehaviorQueue(this, e => e && e.id === "fight_rival"); + clearForcedBehaviorQueue(this, e => e && e.id === "fight_rival"); this.setActionState?.("idle", { target: null, reason: "\u76f8\u624b\u3092\u898b\u5931\u3063\u305f\u3002", sleeping: false, clearTarget: true }); return; } const reasonText = best ? `${best.name || "\u76f8\u624b"}\u304c\u6c17\u306b\u5165\u3089\u306a\u3044\u306e\u3067\u3001\u55a7\u5629\u3057\u3066\u3044\u308b\u3002` : "\u6c17\u306b\u5165\u3089\u306a\u3044\u76f8\u624b\u304c\u3044\u308b\u306e\u3067\u3001\u55a7\u5629\u3057\u3066\u3044\u308b\u3002"; this.setActionState?.("fight", { target: best, reason: reasonText }); - if (typeof setBehaviorText === "function") setBehaviorText(this, { need: "social", subNeed: "conflict", actionId: "fight_rival", actionLabel: "\u55a7\u5629\u3057\u3066\u3044\u308b", reasonText, target: best, phase: "perform", source: "behavior" }); + setBehaviorText(this, { need: "social", subNeed: "conflict", actionId: "fight_rival", actionLabel: "\u55a7\u5629\u3057\u3066\u3044\u308b", reasonText, target: best, phase: "perform", source: "behavior" }); this.bubble("!", 1.6); return; } @@ -662,7 +655,7 @@ function resolveNeedsCore(dt) { const shockTriggered = safetyDelta >= 40 || healthDelta >= 45 || fulfillDelta >= 50; if (breaker && shockTriggered && !["panic", "intimidate", "fight", "birth_ritual"].includes(this.state)) { if (startNeedDrivenEmergencyReaction(this, breaker, "\u30d1\u30cb\u30c3\u30af\u306b\u306a\u3063\u3066\u3044\u308b\u3002")) return; - if (typeof clearTarinaiBehavior === "function") clearTarinaiBehavior(this, { reason: this.thought }); else this.behavior = null; + clearTarinaiBehavior(this, { reason: this.thought }); this.lastNeedShockBreaker = null; return; } @@ -671,7 +664,7 @@ function resolveNeedsCore(dt) { if ((this.behaviorSwitchCooldownUntil || 0) > (this.world?.time || 0) && !this.hasForcedBehavior?.()) { this.behaviorLockTimer = Math.max(this.behaviorLockTimer || 0, 0.25); - if (!(typeof currentTarinaiBehavior === "function" ? currentTarinaiBehavior(this) : this.behavior) && !["panic", "fight", "birth_ritual", "intimidate", "sleep"].includes(this.state)) this.setActionState?.("idle", { target: null, reason: this.thought || "\u5c11\u3057\u69d8\u5b50\u3092\u898b\u3066\u3044\u308b", sleeping: false, clearTarget: true }); + if (!(currentTarinaiBehavior(this)) && !["panic", "fight", "birth_ritual", "intimidate", "sleep"].includes(this.state)) this.setActionState?.("idle", { target: null, reason: this.thought || "\u5c11\u3057\u69d8\u5b50\u3092\u898b\u3066\u3044\u308b", sleeping: false, clearTarget: true }); return; } @@ -723,10 +716,6 @@ function resolveNeedsCore(dt) { resolveNeedsCore.call(tarinai, dt); return true; }, - updateWithThis(dt = 0) { - return this && !this.dead ? (resolveNeedsCore.call(this, dt), true) : false; - }, - resolveNeedsCore, }); } Object.defineProperties(Tarinai.prototype, Object.getOwnPropertyDescriptors({ @@ -856,10 +845,7 @@ function resolveNeedsCore(dt) { }, sunbathSpriteCandidates() { - const sprites = typeof SPRITES !== "undefined" ? SPRITES : (typeof globalThis !== "undefined" ? globalThis.SPRITES : []); - const ids = Array.isArray(sprites) - ? sprites.map(s => s?.id).filter(id => typeof id === "string" && id.startsWith("sunbath")) - : []; + const ids = SPRITES.map(s => s?.id).filter(id => typeof id === "string" && id.startsWith("sunbath")); const ordered = ids.length ? ids : ["sunbath_1", "sunbath_2", "sunbath_3"]; return ordered.slice().sort((a, b) => { const an = Number(String(a).match(/(\d+)$/)?.[1] || 0); @@ -871,10 +857,7 @@ function resolveNeedsCore(dt) { preloadSunbathSprites() { const ids = this.sunbathSpriteCandidates ? this.sunbathSpriteCandidates() : ["sunbath_1", "sunbath_2", "sunbath_3"]; - for (const id of ids) { - if (typeof ensureImage === "function") ensureImage(id); - else if (globalThis.TarinaiAssets?.ensureImage) globalThis.TarinaiAssets.ensureImage(id); - } + for (const id of ids) ensureImage(id); }, normalizeSunbathSpriteId(value = "") { @@ -886,9 +869,7 @@ function resolveNeedsCore(dt) { isSunbathSpriteReady(id = "") { if (!id) return false; - if (typeof isImageReady === "function") return isImageReady(id); - const img = typeof images !== "undefined" ? images.get(id) : globalThis.TarinaiAssets?.images?.get?.(id); - return !!(img && img.complete && (img.naturalWidth || img.width)); + return isImageReady(id); }, randomSunbathSpriteId(exclude = "", options = {}) { @@ -1001,7 +982,6 @@ function resolveNeedsCore(dt) { this.sunbathCooldown = 0; this.sunbathFrameTimer = 0.5; this.sunbathSpriteVariant = this.randomSunbathSpriteId ? this.randomSunbathSpriteId("", { readyOnly: true }) : `sunbath_${1 + Math.floor(Math.random() * 3)}`; - this.sunbathSpriteFrameKey = Math.floor((this.world?.time || 0) / 0.5); this._activeSunbathSpriteId = this.sunbathSpriteId ? this.sunbathSpriteId() : this.sunbathSpriteVariant; this._lastRenderableSunbathSpriteId = this._activeSunbathSpriteId; this.visibleSpriteId = this._activeSunbathSpriteId; @@ -1009,25 +989,22 @@ function resolveNeedsCore(dt) { this.sunbathRecoveryChecked = false; this.vx = 0; this.vy = 0; - if (typeof setBehaviorText === "function") setBehaviorText(this, { need: options.leisure ? "fulfill" : "health", subNeed: "sunbath", actionId: "sunbath", actionLabel: "\u65e5\u5149\u6d74\u3057\u3066\u3044\u308b", reason: options.leisure ? "\u4f59\u88d5\u304c\u3042\u308b\u306e\u3067\u3001\u65e5\u5149\u6d74\u3057\u3066\u3044\u308b\u3002" : "\u8abf\u5b50\u304c\u60aa\u3044\u306e\u3067\u3001\u65e5\u5149\u6d74\u3057\u3066\u3044\u308b\u3002", target: null, phase: "perform", source: "behavior" }); + setBehaviorText(this, { need: options.leisure ? "fulfill" : "health", subNeed: "sunbath", actionId: "sunbath", actionLabel: "\u65e5\u5149\u6d74\u3057\u3066\u3044\u308b", reason: options.leisure ? "\u4f59\u88d5\u304c\u3042\u308b\u306e\u3067\u3001\u65e5\u5149\u6d74\u3057\u3066\u3044\u308b\u3002" : "\u8abf\u5b50\u304c\u60aa\u3044\u306e\u3067\u3001\u65e5\u5149\u6d74\u3057\u3066\u3044\u308b\u3002", target: null, phase: "perform", source: "behavior" }); if ((this.world?.time || 0) >= (this.nextBubbleAt || 0) && Math.random() < 0.45) this.bubble("\u307d\u304b\u3063", 4.0, "rgba(112,86,36,0.78)"); return true; }, updateSunbath(dt) { - return globalThis.TarinaiSunbathSystem?.updateOne(this, dt) || false; + return globalThis.TarinaiSunbathSystem.updateOne(this, dt) || false; }, applyCursorContactCare(dt) { - return globalThis.TarinaiCursorCareSystem?.updateOne(this, dt) || false; + return globalThis.TarinaiCursorCareSystem.updateOne(this, dt) || false; }, - update(dt) { - return globalThis.TarinaiUpdatePipeline?.updateOne?.(this, dt) || false; - }, applyEnvironment(dt) { - return globalThis.TarinaiLocalEnvironmentSystem?.updateOne(this, dt) || false; + return globalThis.TarinaiLocalEnvironmentSystem.updateOne(this, dt) || false; }, bubble(text, cooldown = 2.4, color = "rgba(42,36,29,0.78)", options = {}) { @@ -1050,7 +1027,7 @@ function resolveNeedsCore(dt) { }, isServingFoodItem(it) { if (it?.type === "duplicator" && it.storedFoodType) return true; - return Boolean(it && typeof isServingFoodType === "function" && isServingFoodType(it.type)); + return Boolean(it && isServingFoodType(it.type)); }, foodItemHasServingLeft(it) { @@ -1064,7 +1041,7 @@ function resolveNeedsCore(dt) { ensureFoodServings(it) { if (it?.type === "duplicator") return; if (!this.isServingFoodItem(it)) return; - const max = typeof foodServingsForSize === "function" ? foodServingsForSize(it.toolSize || "medium") : 5; + const max = foodServingsForSize(it.toolSize || "medium"); if (!Number.isFinite(it.foodServingsMax) || it.foodServingsMax <= 0) it.foodServingsMax = max; if (!Number.isFinite(it.foodServingsRemaining)) { const original = { food: 85, sweet: 62, love_mochi: 62, fight_mochi: 62, sleep_drug: 58, laxative: 58, protein: 58, niteropu: 58, ammo: 48, mystery_drug: 52, mercury: 44, giant_drug: 52, dwarf_drug: 52, zunda_juice: 70 }[it.type] || 62; @@ -1078,14 +1055,14 @@ function resolveNeedsCore(dt) { consumeFoodServing(it, nutrition) { if (it?.type === "duplicator") { if (!it.storedFoodType) return 0; - const value = Number.isFinite(nutrition) ? nutrition : (window.TarinaiItemRegistry?.food?.nutrition?.(it.storedFoodType, 0) ?? 42); + const value = Number.isFinite(nutrition) ? nutrition : (window.TarinaiItemRegistry.food?.nutrition?.(it.storedFoodType, 0) ?? 42); it.usedCount = (it.usedCount || 0) + 1; this.world?.emit?.("item:eaten", { tarinai: this, item: it, type: it.storedFoodType, sourceType: "duplicator", nutrition: value }); return value; } this.ensureFoodServings(it); if (!this.isServingFoodItem(it) || (it.foodServingsRemaining || 0) <= 0) return 0; - const value = Number.isFinite(nutrition) ? nutrition : (window.TarinaiItemRegistry?.food?.nutrition?.(it.type, 0) ?? 0); + 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; this.world?.emit?.("item:eaten", { tarinai: this, item: it, type: it.type, nutrition: value }); @@ -1093,7 +1070,7 @@ function resolveNeedsCore(dt) { }, foodHungerReliefFor(type = "", nutrition = 0, fallbackMultiplier = 0) { - const relief = window.TarinaiItemRegistry?.food?.hungerRelief?.(type, fallbackMultiplier) ?? fallbackMultiplier; + const relief = window.TarinaiItemRegistry.food?.hungerRelief?.(type, fallbackMultiplier) ?? fallbackMultiplier; return (Number(nutrition) || 0) * relief; }, @@ -1154,7 +1131,7 @@ function resolveNeedsCore(dt) { }, resolveNeeds(dt) { - return global.TarinaiNeedPlannerSystem?.updateOne?.(this, dt) ?? global.TarinaiNeedPlannerCoreSystem?.updateOne?.(this, dt); + return global.TarinaiNeedPlannerSystem.updateOne(this, dt) ?? global.TarinaiNeedPlannerCoreSystem.updateOne(this, dt); }, makePoop(force = 1) { @@ -1183,7 +1160,7 @@ function resolveNeedsCore(dt) { }, interactWithItems(dt) { - return window.TarinaiItemInteractionSystem?.updateOne?.(this, dt); + return window.TarinaiItemInteractionSystem.updateOne(this, dt); } })); })(typeof window !== "undefined" ? window : globalThis); diff --git a/js/tarinai_nest_sleep_system.js b/js/tarinai_nest_sleep_system.js index 7967abc..ff90883 100644 --- a/js/tarinai_nest_sleep_system.js +++ b/js/tarinai_nest_sleep_system.js @@ -4,10 +4,9 @@ // Centralizes sleep furniture, nest occupancy/reservation, and sleep-target // selection policy so nest-box entrance logic and bed targeting do not drift. (function (global) { - const math = global.TarinaiMath || {}; - const distFn = math.dist || ((a, b) => Math.hypot((a?.x || 0) - (b?.x || 0), (a?.y || 0) - (b?.y || 0))); - const distXYFn = math.distXY || ((ax, ay, bx, by) => Math.hypot((ax || 0) - (bx || 0), (ay || 0) - (by || 0))); - const clampFn = math.clamp || ((v, a, b) => Math.max(a, Math.min(b, v))); + const distFn = typeof dist === "function" ? dist : ((a, b) => Math.hypot((a?.x || 0) - (b?.x || 0), (a?.y || 0) - (b?.y || 0))); + const distXYFn = typeof distXY === "function" ? distXY : ((ax, ay, bx, by) => Math.hypot((ax || 0) - (bx || 0), (ay || 0) - (by || 0))); + const clampFn = typeof clamp === "function" ? clamp : ((v, a, b) => Math.max(a, Math.min(b, v))); function isSleepFurniture(item) { return Boolean(item && (item.roles?.sleepPlace || (typeof global.isSleepFurnitureType === "function" ? global.isSleepFurnitureType(item.type) : (item.type === "bed" || item.type === "nest_box" || item.type === "grass_bed")))); @@ -137,15 +136,11 @@ isSleepFurniture, isTarinaiHiddenInNestBox, nestBoxOccupants, - nestBoxReservationCount, bedOccupancy, bedComfort, sleepPlaceAvailableFor, nearestSleepPlaceOfType, - findOwnedStructure, - findUnownedStructure, findPrioritySleepPlaceBeforeGrassBedBuild, findNearestSleepPlace, - distXY: distXYFn, }); })(typeof window !== "undefined" ? window : globalThis); diff --git a/js/tarinai_render.js b/js/tarinai_render.js index 1a7c379..35590e1 100644 --- a/js/tarinai_render.js +++ b/js/tarinai_render.js @@ -9,7 +9,7 @@ if (worldRef.selected === tarinai || tarinai.favorite) return true; if ((tarinai.hurtTimer || 0) > 0.04 || (tarinai.eatTimer || 0) > 0.02 || (tarinai.pokeFlashTimer || 0) > 0.03) return true; if ((tarinai.focusPulseTimer || 0) > 0.04 || tarinai.state === "cursor_friend" || tarinai.state === "cursor_enemy") return true; - const tier = window.TarinaiPerf?.renderQualityTier?.() || "high"; + const tier = window.TarinaiPerf.renderQualityTier() || "high"; if (tier === "low") return false; if (tier === "mid") { const p = worldRef.pointer; @@ -27,14 +27,8 @@ this.preloadSunbathSprites?.(); const ids = this.sunbathSpriteCandidates ? this.sunbathSpriteCandidates() : ["sunbath_1", "sunbath_2", "sunbath_3"]; const candidates = (Array.isArray(ids) && ids.length ? ids : ["sunbath_1", "sunbath_2", "sunbath_3"]).filter(id => String(id || "").startsWith("sunbath")); - const ready = (id) => typeof isImageReady === "function" - ? isImageReady(id) - : !!(typeof images !== "undefined" && images?.get?.(id)?.complete && (images?.get?.(id)?.naturalWidth || images?.get?.(id)?.width)); - const imageFor = (id) => { - if (!id) return null; - if (typeof getRenderableImage === "function") return getRenderableImage(id, ""); - return (typeof images !== "undefined" ? images.get?.(id) : globalThis.TarinaiAssets?.images?.get?.(id)) || null; - }; + const ready = (id) => isImageReady(id); + const imageFor = (id) => id ? getRenderableImage(id, "") : null; let requested = this.sunbathSpriteId ? this.sunbathSpriteId() : this.normalizeSunbathSpriteId?.(this.sunbathSpriteVariant); if (!requested || !candidates.includes(requested)) requested = candidates[0] || "sunbath_1"; @@ -54,14 +48,10 @@ } else { const fallbackId = this._lastNonSunbathSpriteId || this.goodMode || this.type || "smile"; renderSprite = fallbackId; - img = typeof getRenderableImage === "function" - ? getRenderableImage(fallbackId, "smile") - : (typeof images !== "undefined" ? (images.get?.(fallbackId) || images.get?.("smile")) : null); + img = getRenderableImage(fallbackId, "smile"); if (!img) { renderSprite = "smile"; - img = typeof getRenderableImage === "function" - ? getRenderableImage("smile", "smile") - : (typeof images !== "undefined" ? images.get?.("smile") : null); + img = getRenderableImage("smile", "smile"); } } @@ -157,17 +147,14 @@ const sunbathSprite = String(sprite || "").startsWith("sunbath"); const sunbathCandidates = sunbathSprite && this.sunbathSpriteCandidates ? this.sunbathSpriteCandidates() : []; if (sunbathSprite) { - for (const id of sunbathCandidates) { - if (typeof ensureImage === "function") ensureImage(id); - else if (globalThis.TarinaiAssets?.ensureImage) globalThis.TarinaiAssets.ensureImage(id); - } + for (const id of sunbathCandidates) ensureImage(id); } const spriteFallback = "smile"; let renderSprite = sprite; - let img = typeof getRenderableImage === "function" ? getRenderableImage(sprite, spriteFallback) : (images.get(sprite) || images.get(spriteFallback) || images.get(this.type)); + let img = getRenderableImage(sprite, spriteFallback); if (sunbathSprite) { - const ready = (id) => typeof isImageReady === "function" ? isImageReady(id) : !!(images?.get?.(id)?.complete || images?.get?.(id)?.naturalWidth || images?.get?.(id)?.width); - const imageFor = (id) => (typeof images !== "undefined" ? images.get?.(id) : globalThis.TarinaiAssets?.images?.get?.(id)) || null; + const ready = (id) => isImageReady(id); + const imageFor = (id) => images.get(id) || null; if (ready(sprite)) { this._lastRenderableSunbathSpriteId = sprite; } else { @@ -184,7 +171,7 @@ renderSprite = this._lastRenderableSunbathSpriteId; img = imageFor(this._lastRenderableSunbathSpriteId); } - if (!img) img = typeof getRenderableImage === "function" ? getRenderableImage("smile", "smile") : (images.get("smile") || images.get(this.type)); + if (!img) img = getRenderableImage("smile", "smile"); } const metrics = getImageMetrics(renderSprite) || getImageMetrics(sprite) || getImageMetrics(this.type) || getImageMetrics("smile"); const w = metrics?.w || 512; @@ -435,13 +422,13 @@ const dh = drawH / squish; const brightness = clamp(0.82 + lightState.light * 0.30 + lightState.dawnStrength * 0.08 + lightState.goldenStrength * 0.06 + lightState.noonStrength * 0.04, 0.80, 1.17); const saturation = clamp(0.86 + lightState.light * 0.22 + lightState.goldenStrength * 0.10 - lightState.nightStrength * 0.04, 0.82, 1.14); - const renderScale = typeof SPRITE_CACHE_SCALE === "number" ? SPRITE_CACHE_SCALE : 2.0; + const renderScale = SPRITE_CACHE_SCALE; const cacheW = Math.max(1, Math.round((dw * renderScale) / 8) * 8); const cacheH = Math.max(1, Math.round((dh * renderScale) / 8) * 8); if (img) { let spriteCacheKey = renderSprite || sprite; try { - if (typeof images !== "undefined" && images.get?.(renderSprite || sprite) !== img) { + if (images.get(renderSprite || sprite) !== img) { let fallbackId = "fallback"; for (const [imageId, imageObj] of images) { if (imageObj === img) { fallbackId = imageId; break; } @@ -451,7 +438,7 @@ } catch (err) { spriteCacheKey = renderSprite || sprite; } - const spriteCanvas = typeof getCachedSpriteCanvas === "function" ? getCachedSpriteCanvas(spriteCacheKey, img, cacheW, cacheH) : img; + const spriteCanvas = getCachedSpriteCanvas(spriteCacheKey, img, cacheW, cacheH); ctx.drawImage(spriteCanvas, -dw / 2, -dh / 2, dw, dh); } else { ctx.save(); @@ -464,7 +451,7 @@ ctx.stroke(); ctx.restore(); } - if (img && (this.zunchiStain || 0) > 3 && typeof getCachedStainOverlayCanvas === "function") { + if (img && (this.zunchiStain || 0) > 3) { const stainCanvas = getCachedStainOverlayCanvas(renderSprite || sprite, img, cacheW, cacheH, clamp((this.zunchiStain || 0) / 100, 0, 1), this.zunchiStainSeed || 0.5); if (stainCanvas) { ctx.save(); @@ -474,7 +461,7 @@ } } if (String(sprite || "").startsWith("zunchi_slave")) { - const zImg = typeof getRenderableImage === "function" ? getRenderableImage("zunchi", "zunchi") : (images && images.get ? images.get("zunchi") : null); + const zImg = getRenderableImage("zunchi", "zunchi"); if (zImg) { ctx.save(); ctx.shadowColor = "rgba(46, 36, 24, 0.28)"; diff --git a/js/tarinai_runtime.js b/js/tarinai_runtime.js index 791825f..b41ff7a 100644 --- a/js/tarinai_runtime.js +++ b/js/tarinai_runtime.js @@ -19,12 +19,12 @@ } function updateOne(tarinai, dt, options = {}) { - if (!tarinai || tarinai.dead || !global.TarinaiUpdatePipeline?.updateOne) return false; + if (!tarinai || tarinai.dead || !global.TarinaiUpdatePipeline.updateOne) return false; return global.TarinaiUpdatePipeline.updateOne(tarinai, dt, options) !== false; } function updateMotionOnly(tarinai, dt, options = {}) { - if (!tarinai || tarinai.dead || !global.TarinaiMovementUpdateStep?.update) return false; + if (!tarinai || tarinai.dead || !global.TarinaiMovementUpdateStep.update) return false; tarinai.prevX = tarinai.x; tarinai.prevY = tarinai.y; const motionDt = Math.max(0.001, Math.min(0.05, Number(options.context?.motionDt ?? dt) || 0.016)); @@ -35,13 +35,7 @@ })?.done !== true || !tarinai.dead; } - function updateBatch(tarinaiList, dt) { - if (!Array.isArray(tarinaiList)) return 0; - let ran = 0; - for (const t of tarinaiList) if (updateOne(t, dt)) ran += 1; - return ran; - } - global.TarinaiCreatureRuntime = Object.freeze({ installPrototypeBoundary, updateOne, updateMotionOnly, updateBatch }); + global.TarinaiCreatureRuntime = Object.freeze({ updateOne, updateMotionOnly }); installPrototypeBoundary(); })(typeof window !== "undefined" ? window : globalThis); diff --git a/js/tarinai_seed_factory.js b/js/tarinai_seed_factory.js index c66afb3..86bfce8 100644 --- a/js/tarinai_seed_factory.js +++ b/js/tarinai_seed_factory.js @@ -67,8 +67,7 @@ } function seedUnit(seed = "", key = "") { - if (typeof stableUnit === "function") return stableUnit(seed, key); - return parseInt(hashString(`${seed}:${key}`).slice(0, 6), 36) / (36 ** 6); + return stableUnit(seed, key); } function seedPick(seed = "", key = "", list = []) { @@ -149,6 +148,5 @@ prepareAddOptions, birthProfile, personalityArray, - hashString, }); })(typeof window !== "undefined" ? window : globalThis); diff --git a/js/tarinai_social_action_runtime.js b/js/tarinai_social_action_runtime.js index eafcdf2..4d53d9b 100644 --- a/js/tarinai_social_action_runtime.js +++ b/js/tarinai_social_action_runtime.js @@ -20,7 +20,7 @@ function triggerFriendContact(t, other, world, dt = 0) { if (typeof applyNeedRelief === "function") applyNeedRelief(t, { social: -16, fulfill: -3 }); if ((world.time || 0) >= (t.nextBubbleAt || 0)) t.bubble?.("\u306f\u3046", 2.2, "rgba(92,132,78,0.78)"); t.setActionState?.("idle", { target: other, reason: `${other.name || "\u4ef2\u9593"}\u3068\u4e00\u7dd2\u306b\u3044\u308b`, sleeping: false }); - if (typeof setBehaviorText === "function") setBehaviorText(t, { need: "social", subNeed: "bond", actionId: "approach_friend", actionLabel: "\u4ef2\u9593\u3068\u4e00\u7dd2\u306b\u3044\u308b", reason: `${other.name || "\u4ef2\u9593"}\u3068\u4e00\u7dd2\u306b\u3044\u308b`, target: other, phase: "perform", source: "behavior" }); + setBehaviorText(t, { need: "social", subNeed: "bond", actionId: "approach_friend", actionLabel: "\u4ef2\u9593\u3068\u4e00\u7dd2\u306b\u3044\u308b", reason: `${other.name || "\u4ef2\u9593"}\u3068\u4e00\u7dd2\u306b\u3044\u308b`, target: other, phase: "perform", source: "behavior" }); return "finished"; } @@ -43,8 +43,8 @@ function updateSocialContactBehavior(t, world, dt, mode = "bond") { } const mate = mateTargetFor(t, world, 150); const parts = t.socialReasonParts || {}; - const selfBehaviorId = typeof getTarinaiBehaviorId === "function" ? getTarinaiBehaviorId(t) : t.behavior?.actionId; - const otherBehaviorId = typeof getTarinaiBehaviorId === "function" ? getTarinaiBehaviorId(other) : other.behavior?.actionId; + const selfBehaviorId = getTarinaiBehaviorId(t); + const otherBehaviorId = getTarinaiBehaviorId(other); const mateIntent = mode === "mate" || selfBehaviorId === "approach_mate" || otherBehaviorId === "approach_mate" || (t.loveMochiTimer || 0) > 0.04 || (other.loveMochiTimer || 0) > 0.04; const mateDominant = Number(parts.mate || 0) >= Math.max(Number(parts.bond || 0), Number(parts.conflict || 0)) + 10; if (mate === other && (t.reproductionTimer || 0) <= 8 && (other.reproductionTimer || 0) <= 10 && (mateIntent || mateDominant)) { @@ -58,7 +58,7 @@ function updateMateBehavior(t, world, dt) { if (!other) return false; const d = dist(t, other); if (d <= Math.max(50, (t.radius || 20) + (other.radius || 20) + 14)) { - const otherWantsMate = (typeof getTarinaiBehaviorId === "function" ? getTarinaiBehaviorId(other) : other.behavior?.actionId) === "approach_mate" || (other.loveMochiTimer || 0) > 0.04 || (other.reproductionTimer || 0) <= 4; + const otherWantsMate = (getTarinaiBehaviorId(other)) === "approach_mate" || (other.loveMochiTimer || 0) > 0.04 || (other.reproductionTimer || 0) <= 4; const selfForced = isCurrentBehaviorForced(t) || (t.loveMochiTimer || 0) > 0.04; if ((otherWantsMate || selfForced || (t.reproductionTimer || 0) <= 4) && world.startBirthRitual?.(t, other)) return "finished"; moveToOrUse(t, other, "seek_friend", "\u7e41\u6b96\u3067\u304d\u308b\u76f8\u624b\u306b\u8fd1\u3065\u3044\u3066\u3044\u308b"); @@ -80,7 +80,7 @@ function updateFightBehavior(t, world, dt) { t.counterAttackFromId = null; t.fightCooldown = Math.max(t.fightCooldown || 0, CONFIG.fightCooldown + deterministicRange(world, "fight-target-lost-cooldown", 4, 8, t, rival)); if (isLiveTarinaiEntity(rival)) world.markFightPairCooldown?.(t, rival, CONFIG.fightCooldown + 6); - if (typeof clearForcedBehaviorQueue === "function") clearForcedBehaviorQueue(t, e => e && e.id === "fight_rival"); + clearForcedBehaviorQueue(t, e => e && e.id === "fight_rival"); t.setActionState?.("idle", { target: null, reason: "\u76f8\u624b\u3092\u898b\u5931\u3063\u305f\u3002", sleeping: false, clearTarget: true }); return true; } diff --git a/js/tarinai_social_move_life.js b/js/tarinai_social_move_life.js index 3e871ce..9bdb120 100644 --- a/js/tarinai_social_move_life.js +++ b/js/tarinai_social_move_life.js @@ -199,8 +199,8 @@ queueForcedTarinaiBehavior(this, "approach_mate", { target: o, source: "love_mochi", priority: 150, ttl: 10, causeText: "\u3078\u3053\u9905\u306e\u52b9\u679c" }); queueForcedTarinaiBehavior(o, "approach_mate", { target: this, source: "love_mochi", priority: 150, ttl: 10, causeText: "\u3078\u3053\u9905\u306e\u52b9\u679c" }); } - const selfBehaviorId = typeof getTarinaiBehaviorId === "function" ? getTarinaiBehaviorId(this) : this.behavior?.actionId; - const otherBehaviorId = typeof getTarinaiBehaviorId === "function" ? getTarinaiBehaviorId(o) : o.behavior?.actionId; + const selfBehaviorId = getTarinaiBehaviorId(this); + const otherBehaviorId = getTarinaiBehaviorId(o); const mateIntent = selfBehaviorId === "approach_mate" || otherBehaviorId === "approach_mate" || loveDrug; if (mateIntent && deterministicChance(this.world, "birth-ritual-start", dt * (loveDrug ? 0.78 : 0.30), this, o)) { this.world.startBirthRitual(this, o); @@ -376,7 +376,7 @@ const d = Math.hypot(dx, dy) || 1; if (this.state === "wander" && this.target?.detour && d < Math.max(22, (this.radius || 20) * 0.9)) { if (typeof applyNeedSatisfaction === "function") applyNeedSatisfaction(this, { fulfill: 6 }, "wander"); - if (typeof clearTarinaiBehavior === "function") clearTarinaiBehavior(this, { reason: this.thought }); else this.behavior = null; + clearTarinaiBehavior(this, { reason: this.thought }); this.behaviorLockTimer = 0; this.goIdle?.("\u5c11\u3057\u6b69\u3044\u305f"); return; diff --git a/js/tarinai_sunbath_system.js b/js/tarinai_sunbath_system.js index 3712db2..c0e87ae 100644 --- a/js/tarinai_sunbath_system.js +++ b/js/tarinai_sunbath_system.js @@ -43,7 +43,6 @@ } global.TarinaiSunbathSystem = Object.freeze({ - updateBody, updateOne, }); })(typeof window !== "undefined" ? window : globalThis); diff --git a/js/tarinai_update_legacy_system.js b/js/tarinai_update_legacy_system.js deleted file mode 100644 index ef01f6b..0000000 --- a/js/tarinai_update_legacy_system.js +++ /dev/null @@ -1,25 +0,0 @@ -"use strict"; - -// Layer: entity-runtime/tarinai/legacy -// Compatibility shim. The former monolithic update body was removed so the -// concrete TarinaiUpdatePipeline remains the single runtime path. -(function (global) { - function legacyUpdate(dt) { - return global.TarinaiUpdatePipeline?.updateOne?.(this, dt) || false; - } - - function updateOne(tarinai, dt) { - if (!tarinai || tarinai.dead) return false; - return global.TarinaiUpdatePipeline?.updateOne?.(tarinai, dt) !== false; - } - - function updateWithThis(dt) { - return updateOne(this, dt); - } - - global.TarinaiLegacyUpdateSystem = Object.freeze({ - legacyUpdate, - updateOne, - updateWithThis, - }); -})(typeof window !== "undefined" ? window : globalThis); diff --git a/js/tarinai_update_pipeline.js b/js/tarinai_update_pipeline.js index 10dea01..47c0632 100644 --- a/js/tarinai_update_pipeline.js +++ b/js/tarinai_update_pipeline.js @@ -12,30 +12,27 @@ "TarinaiHealthUpdateStep", ]); + let cachedSteps = null; function concreteSteps() { - return CONCRETE_STEP_NAMES.map(name => global[name]).filter(step => step && typeof step.update === "function"); + if (!cachedSteps) cachedSteps = CONCRETE_STEP_NAMES.map(name => global[name]); + return cachedSteps; } - function canRunConcretePipeline() { - return concreteSteps().length === CONCRETE_STEP_NAMES.length; - } function updateOne(tarinai, dt, options = {}) { - if (!tarinai || tarinai.dead || !canRunConcretePipeline()) return false; + if (!tarinai || tarinai.dead) return false; + const steps = concreteSteps(); + if (steps.length !== CONCRETE_STEP_NAMES.length) return false; const context = { ...(options.context || {}), wasSleepingAtFrameStart: false, }; - for (const step of concreteSteps()) { + for (const step of steps) { const result = step.update(tarinai, dt, context) || {}; if (result.done) return result.ok !== false; } return true; } - global.TarinaiUpdatePipeline = Object.freeze({ - stepNames: CONCRETE_STEP_NAMES, - canRunConcretePipeline, - updateOne, - }); + global.TarinaiUpdatePipeline = Object.freeze({ updateOne }); })(typeof window !== "undefined" ? window : globalThis); diff --git a/js/tarinai_update_policy.js b/js/tarinai_update_policy.js index 7230463..2746c72 100644 --- a/js/tarinai_update_policy.js +++ b/js/tarinai_update_policy.js @@ -5,10 +5,10 @@ // thinking/environment work, while visible movement can still be integrated // every frame via the smooth-motion pass. (function (global) { - const helpers = () => global.TarinaiSimulationRuntime?.helpers || {}; + const helpers = global.TarinaiSimulationRuntime.helpers; function behaviorOf(tarinai) { - return typeof currentTarinaiBehavior === "function" ? currentTarinaiBehavior(tarinai) : tarinai?.behavior; + return currentTarinaiBehavior(tarinai); } function behaviorId(tarinai) { @@ -17,7 +17,7 @@ } function visible(worldRef, tarinai, visibleRect) { - return Boolean(helpers().pointInRect?.(tarinai.x || 0, tarinai.y || 0, visibleRect)); + return Boolean(helpers.pointInRect(tarinai.x || 0, tarinai.y || 0, visibleRect)); } function hasMotionNeed(tarinai) { @@ -34,12 +34,12 @@ const radius = Math.max(150, (Number(tarinai.radius) || 22) + 190); const source = worldRef.nearbyItems?.(tarinai.x || 0, tarinai.y || 0, radius, true) || worldRef.items || []; for (const it of source) { - if (!it || it.dead || !global.TarinaiMechanicalSystem?.isMechanicalType?.(it.type)) continue; - const reach = global.TarinaiMechanicalSystem?.reach?.(it) || Math.max(64, it.r || 64); + if (!it || it.dead || !global.TarinaiMechanicalSystem.isMechanicalType(it.type)) continue; + const reach = global.TarinaiMechanicalSystem.reach(it) || Math.max(64, it.r || 64); if (Math.hypot((tarinai.x || 0) - (it.x || 0), (tarinai.y || 0) - (it.y || 0)) > reach + radius) continue; if (it.type === "rotator" || it.type === "reciprocator") return true; - if (global.TarinaiMechanicalSystem?.passiveItemAwake?.(it)) return true; - const body = global.TarinaiPhysicsBodySystem?.ensureBody?.(it, worldRef, { syncFromLegacy: false }); + if (global.TarinaiMechanicalSystem.passiveItemAwake(it)) return true; + const body = global.TarinaiPhysicsBodySystem.ensureBody(it, worldRef, { syncFromLegacy: false }); const vel = body?.velocity || {}; if (Math.hypot(Number(vel.x || it.vx || 0) || 0, Number(vel.y || it.vy || 0) || 0) + Math.abs(Number(vel.angular || it.spin || 0) || 0) * 64 > 0.8) return true; } @@ -70,12 +70,12 @@ const id = behaviorId(tarinai); const state = String(tarinai.state || id || ""); - const tier = global.TarinaiPerf?.renderQualityTier?.() || "high"; + const tier = global.TarinaiPerf.renderQualityTier() || "high"; const count = (worldRef.tarinai || []).length; - const mobile = Boolean(global.TarinaiInputMode?.snapshot?.().touchFirst || (global.innerWidth || 9999) <= 760); + 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) || visibleRect); - const isNear = isVisible || Boolean(helpers().pointInRect?.(tarinai.x || 0, tarinai.y || 0, farRect)); + const farRect = isVisible ? visibleRect : helpers.updateVisibleWorldRect(worldRef, 520); + const isNear = isVisible || Boolean(helpers.pointInRect(tarinai.x || 0, tarinai.y || 0, farRect)); const motionNeed = hasMotionNeed(tarinai); const nestEntryActive = Boolean(tarinai.insideNestBoxId && (typeof tarinai.nestBoxEntryActive === "function" ? tarinai.nestBoxEntryActive() : ((worldRef.time || 0) < (tarinai._nestBoxEntrySmoothUntil || -Infinity) || ((tarinai.nestFade || 0) > 0.01 && (tarinai.nestFade || 0) < 0.985)))); @@ -140,18 +140,8 @@ return { interval, lane, smoothMotion, visible: isVisible, urgent: false, sleepPhysical, eatPhysical, passivePhysicsNeed }; } - function updateInterval(worldRef, tarinai, visibleRect) { - return cadence(worldRef, tarinai, visibleRect).interval; - } - - function shouldRunRealtime(worldRef, tarinai, visibleRect) { - return updateInterval(worldRef, tarinai, visibleRect) <= 0; - } - global.TarinaiCreatureUpdatePolicy = Object.freeze({ - updateInterval, updateCadence: cadence, behaviorId, - shouldRunRealtime, }); })(typeof window !== "undefined" ? window : globalThis); diff --git a/js/tarinai_update_step_environment.js b/js/tarinai_update_step_environment.js index 7ea8871..ad4223c 100644 --- a/js/tarinai_update_step_environment.js +++ b/js/tarinai_update_step_environment.js @@ -11,8 +11,7 @@ if (t.envTimer >= envInterval && t.world.spendScheduledWork?.("env", urgentAi || t.world.selected === t) !== false) { const envDt = Math.min(t.envTimer, 1.2); t.envTimer = 0; - if (global.TarinaiLocalEnvironmentSystem?.updateOne) global.TarinaiLocalEnvironmentSystem.updateOne(t, envDt); - else t.applyEnvironment?.(envDt); + global.TarinaiLocalEnvironmentSystem.updateOne(t, envDt); } return { done: false }; } diff --git a/js/tarinai_update_step_frame.js b/js/tarinai_update_step_frame.js index d206406..c8a87e6 100644 --- a/js/tarinai_update_step_frame.js +++ b/js/tarinai_update_step_frame.js @@ -7,21 +7,15 @@ // Tarinai.prototype.update body without changing behavior. (function (global) { function detRange(worldRef, salt, min, max, ...parts) { - const fn = global.deterministicRange; - if (typeof fn === "function") return fn(worldRef, salt, min, max, ...parts); - const a = Number(min) || 0; - const b = Number(max) || 0; - return (a + b) * 0.5; + return global.deterministicRange(worldRef, salt, min, max, ...parts); } function detSigned(worldRef, salt, ...parts) { - const fn = global.deterministicSigned; - return typeof fn === "function" ? fn(worldRef, salt, ...parts) : 1; + return global.deterministicSigned(worldRef, salt, ...parts); } function detChance(worldRef, salt, chance, ...parts) { - const fn = global.deterministicChance; - return typeof fn === "function" ? fn(worldRef, salt, chance, ...parts) : (Number(chance) || 0) >= 1; + return global.deterministicChance(worldRef, salt, chance, ...parts); } function update(t, dt, context = {}) { @@ -32,7 +26,6 @@ const minute = dt / 60; t.age += dt; t.reproductionTimer -= dt; - t.blink += dt; t.eatTimer = Math.max(0, t.eatTimer - dt); if (t.state === "eat" && t.eatTimer <= 0.02) { t.setActionState?.("idle", { target: null, reason: "\u98df\u3079\u7d42\u3048\u305f", sleeping: false, clearTarget: true }); @@ -76,18 +69,7 @@ t.checkLife(dt); return { done: true }; } - if (global.TarinaiCursorCareSystem?.updateOne) global.TarinaiCursorCareSystem.updateOne(t, dt); - else t.applyCursorContactCare?.(dt); - const colonyMoodId = t.world?.colonyMood?.id || "relaxed"; - if (colonyMoodId === "weaklings" && !t.isZunchiSlave) applyNeedRelief(t, { safety: -dt * 2, fulfill: -dt * 1 }); - if (colonyMoodId === "zunda_party" || colonyMoodId === "ball_party") applyNeedRelief(t, { fulfill: -dt * 3 }); - if (colonyMoodId === "ant_overrun" && t.state !== "panic") { - const antSeen = (t.world?.nearbyAnts?.(t.x, t.y, 110, true) || []).some(a => a && !a.dead); - if (antSeen) { - if (t.enterPanic) t.enterPanic({ target: null, reason: "\u30a2\u30ea\u3060", fear: 1.1, wake: true, cause: "colony_ant_overrun" }); - else { t.setActionState?.("panic", { target: null, reason: "\u30a2\u30ea\u3060", wake: true }); t.fearTimer = Math.max(t.fearTimer || 0, 1.1 * t.personalityProfile().fear); } - } - } + global.TarinaiCursorCareSystem.updateOne(t, dt); const wasFighting = t.fightTimer > 0.04; t.fightTimer = Math.max(0, t.fightTimer - dt); t.fightCooldown = Math.max(0, t.fightCooldown - dt); @@ -106,7 +88,6 @@ t.fightTargetIds = []; t.fightTargetId = null; } - t.stretchTimer = Math.max(0, t.stretchTimer - dt); t.grassEatTimer = Math.max(0, t.grassEatTimer - dt); t.hurtTimer = Math.max(0, t.hurtTimer - dt); t.fallTimer = Math.max(0, t.fallTimer - dt); diff --git a/js/tarinai_update_step_legacy.js b/js/tarinai_update_step_legacy.js deleted file mode 100644 index 5859a24..0000000 --- a/js/tarinai_update_step_legacy.js +++ /dev/null @@ -1,12 +0,0 @@ -"use strict"; - -// Layer: entity-runtime/tarinai/update-step -// Compatibility shim for old step lists. Delegates to the concrete pipeline. -(function (global) { - function update(t, dt) { - const ok = global.TarinaiUpdatePipeline?.updateOne?.(t, dt); - return { done: true, ok: ok !== false }; - } - - global.TarinaiLegacyUpdateStep = Object.freeze({ update }); -})(typeof window !== "undefined" ? window : globalThis); diff --git a/js/tarinai_update_step_movement.js b/js/tarinai_update_step_movement.js index 5b53fab..4b0db1a 100644 --- a/js/tarinai_update_step_movement.js +++ b/js/tarinai_update_step_movement.js @@ -207,7 +207,7 @@ } else if (dx * dx + dy * dy <= 0.25 && world?.resolveFenceCollision && !world.isTarinaiHiddenInNestBox?.(t)) { const now = world.time || 0; if (now >= (t._nextPassiveSolidRelaxAt || 0)) { - const jitter = typeof stableUnit === "function" ? stableUnit(t.id || t.familyKey || 0, "passive-solid-relax") * 0.55 : Math.random() * 0.55; + const jitter = stableUnit(t.id || t.familyKey || 0, "passive-solid-relax") * 0.55; t._nextPassiveSolidRelaxAt = now + 1.20 + jitter; world.resolveFenceCollision(t); } @@ -235,8 +235,7 @@ const before = Number.isFinite(t._lastPhysicalVelocityX) && Number.isFinite(t._lastPhysicalVelocityY) ? { vx: t._lastPhysicalVelocityX, vy: t._lastPhysicalVelocityY } : current; - if (global.TarinaiSunbathSystem?.updateOne) global.TarinaiSunbathSystem.updateOne(t, motionDt); - else t.updateSunbath?.(motionDt); + global.TarinaiSunbathSystem.updateOne(t, motionDt); t.maintainTargetProgress(motionDt); const prevMotionOnlyFlag = t._motionOnlyPhysicsPass; if (context.motionOnly) t._motionOnlyPhysicsPass = true; @@ -265,7 +264,7 @@ } else if (movedSq <= 0.25 && !world.isTarinaiHiddenInNestBox?.(t)) { const now = world.time || 0; if (now >= (t._nextSolidRelaxAt || 0)) { - const jitter = typeof stableUnit === "function" ? stableUnit(t.id || t.familyKey || 0, "solid-relax") * 0.45 : Math.random() * 0.45; + const jitter = stableUnit(t.id || t.familyKey || 0, "solid-relax") * 0.45; t._nextSolidRelaxAt = now + 0.85 + jitter; world.resolveFenceCollision(t); } @@ -274,5 +273,5 @@ return { done: false }; } - global.TarinaiMovementUpdateStep = Object.freeze({ update, applyVelocityShockDamage, passivePhysicalMotion, markSleepExternalMotion, sleepExternalMotionActive, wakeSleepingFromExternalMotion }); + global.TarinaiMovementUpdateStep = Object.freeze({ update, markSleepExternalMotion, sleepExternalMotionActive, wakeSleepingFromExternalMotion }); })(typeof window !== "undefined" ? window : globalThis); diff --git a/js/text_catalog.js b/js/text_catalog.js index 4765aa9..0df1875 100644 --- a/js/text_catalog.js +++ b/js/text_catalog.js @@ -124,8 +124,6 @@ TEXT_CATALOG.targetLabelOverrides = Object.freeze({ zunchi_sick: "\u305a\u3093\u3061\u75c5", fight_sick: "\u304d\u305a\u3064\u304d\u75c5", }); -TEXT_CATALOG.targetLabels = TEXT_CATALOG.targetLabelOverrides; - TEXT_CATALOG.idleGroundText = function idleGroundText(t = null) { const ground = String(t?.world?.groundType || (typeof world !== "undefined" ? world?.groundType : "") || "soil"); if (ground === "foot_massage") return "\u8db3\u3064\u307c\u306b\u8010\u3048\u3066\u3044\u308b\u3002"; @@ -157,10 +155,8 @@ 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"; - const behaviorState = typeof currentTarinaiBehavior === "function" ? currentTarinaiBehavior(t) : t?.behavior; - const behaviorLabel = typeof globalThis.behaviorText === "function" - ? globalThis.behaviorText(t) - : String(typeof getTarinaiBehaviorText === "function" ? getTarinaiBehaviorText(t) : (behaviorState?.text || behaviorState?.reason || "")).trim(); + const behaviorState = currentTarinaiBehavior(t); + const behaviorLabel = behaviorText(t); const cleanedBehaviorLabel = TEXT_CATALOG.cleanBehaviorText(behaviorLabel, behaviorState?.actionId || t.state || ""); if (cleanedBehaviorLabel) return cleanedBehaviorLabel; const target = TEXT_CATALOG.targetLabel(t.target); @@ -177,7 +173,7 @@ TEXT_CATALOG.reasonLabel = function reasonLabelFromCatalog(t) { if (t.state === "seek_water") return t.explosionDisease ? "\u7206\u767a\u75c5\u3067\u6c34\u3092\u63a2\u3057\u3066\u3044\u308b" : "\u305a\u3093\u3061\u75c5\u3067\u6c34\u3092\u63a2\u3057\u3066\u3044\u308b"; if (t.state === "seek_bed") return target !== "\u306a\u3057" ? `${target}\u3078\u5411\u304b\u3063\u3066\u3044\u308b` : "\u7720\u308b\u5834\u6240\u3092\u63a2\u3057\u3066\u3044\u308b"; if (t.state === "play_ball") return "\u30dc\u30fc\u30eb\u304c\u6c17\u306b\u306a\u3063\u3066\u3044\u308b"; - if (t.state === "seek_friend" && (typeof getTarinaiBehaviorId === "function" ? getTarinaiBehaviorId(t) : t.behavior?.id) === "approach_mate") return t.target?.name ? `${t.target.name}\u306b\u7e41\u6b96\u306e\u305f\u3081\u8fd1\u3065\u3044\u3066\u3044\u308b` : "\u7e41\u6b96\u3067\u304d\u308b\u76f8\u624b\u3092\u63a2\u3057\u3066\u3044\u308b"; + if (t.state === "seek_friend" && (getTarinaiBehaviorId(t)) === "approach_mate") return t.target?.name ? `${t.target.name}\u306b\u7e41\u6b96\u306e\u305f\u3081\u8fd1\u3065\u3044\u3066\u3044\u308b` : "\u7e41\u6b96\u3067\u304d\u308b\u76f8\u624b\u3092\u63a2\u3057\u3066\u3044\u308b"; if (t.state === "seek_friend") return "\u4ef2\u9593\u3092\u63a2\u3057\u3066\u3044\u308b"; if (t.state === "follow_parent") return "\u89aa\u306b\u3064\u3044\u3066\u3044\u304f"; if (t.state === "cursor_friend") return "\u30ab\u30fc\u30bd\u30eb\u306b\u3064\u3044\u3066\u3044\u304f"; @@ -196,12 +192,5 @@ TEXT_CATALOG.reasonLabel = function reasonLabelFromCatalog(t) { return "\u5468\u56f2\u3092\u898b\u3066\u3044\u308b"; }; -function stateLabel(s) { return TEXT_CATALOG.stateLabel(s); } -function targetLabel(target) { return TEXT_CATALOG.targetLabel(target); } -function reasonLabel(t) { return TEXT_CATALOG.reasonLabel(t); } - -window.stateLabel = stateLabel; -window.targetLabel = targetLabel; -window.reasonLabel = reasonLabel; window.TEXT_CATALOG = TEXT_CATALOG; diff --git a/js/ui.js b/js/ui.js index b30951d..07ef649 100644 --- a/js/ui.js +++ b/js/ui.js @@ -63,31 +63,22 @@ const uiCache = { archiveUpdateEnabled: false, toolButtons: [], archiveVersion: "", - archiveRows: [], archiveFamilyVersion: null, - archiveRelationSignature: "", archiveHtmlCache: new Map(), archiveRenderTimer: 0, archiveRenderForce: false, archiveRenderRunning: false, archiveScheduled: false, archivePendingOffscreen: false, - archivePendingWhileRunning: false, archivePendingDirtyAfterRun: false, archiveLastHiddenCheckAt: -Infinity, archiveLastRenderedAt: 0, - archiveLastScheduleFamilyVersion: null, - archiveDiagnostics: { renders: 0, lastMs: 0, lastFamilies: 0, lastNodes: 0, skippedWhileRunning: 0 }, - archiveScrollScheduled: false, deferArchiveUntil: 0, - lastArchiveWindow: "", chartHistory: [], chartBucketStart: -Infinity, chartBucketEnd: -Infinity, chartBucketAccum: null, - lastChartSample: -Infinity, lastChartDraw: "", - lastChartDrawAt: 0, chartMode: "population", canvasDpr: 0, panning: false, @@ -103,7 +94,7 @@ const uiCache = { grabLastWorldX: 0, grabLastWorldY: 0, grabLastSpatialAt: 0, - mobileInputMode: window.TarinaiInputMode?.currentMode || "auto", + mobileInputMode: window.TarinaiInputMode.currentMode || "auto", touchMode: "", touchMoved: false, touchStartX: 0, @@ -112,7 +103,6 @@ const uiCache = { touchLastY: 0, pinchStartDistance: 0, pinchStartZoom: 1, - pinchFocusWorld: null, pinchLastCenterX: 0, pinchLastCenterY: 0, }; diff --git a/js/ui_bind.js b/js/ui_bind.js index fa3621d..c3765cc 100644 --- a/js/ui_bind.js +++ b/js/ui_bind.js @@ -5,9 +5,9 @@ function bindUI() { loadLogPushSettings(); syncLogPushControls(); syncAudioControls(); - window.TarinaiTooltips?.applyToolPalette?.(); + window.TarinaiTooltips.applyToolPalette(); window.bindGroundUI?.(world); - window.TarinaiSaveSystem?.bindSaveSystem?.(); + window.TarinaiSaveSystem.bindSaveSystem(); uiCache.toolButtons = Array.from(ui.toolPalette?.querySelectorAll(".tool") || []); function isActiveToolMode() { return Boolean(world?.tool && world.tool !== "observe"); @@ -33,7 +33,7 @@ function bindUI() { } function isTouchMobileLayout() { - return Boolean(window.TarinaiInputMode?.classification?.touchFirst || document.body.classList.contains("touch-mobile-ui") || window.innerWidth <= 980); + return Boolean(window.TarinaiInputMode.classification?.touchFirst || document.body.classList.contains("touch-mobile-ui") || window.innerWidth <= 980); } function isDesktopLayout() { @@ -63,11 +63,7 @@ function bindUI() { category.querySelector(".tool-category-toggle")?.setAttribute("aria-expanded", collapsed ? "false" : "true"); } - function openAutoCollapsedSectionsForMobileScroll() { - // v39.15.47: 自動スクロールは折り畳み状態を一切変更しない。 - return; - } - + function scrollWindowToElement(target, offset = 0, behavior = "auto") { if (!target) return; const scroller = document.scrollingElement || document.documentElement; @@ -81,7 +77,7 @@ function bindUI() { function scrollPanelToTab(tab = "") { const panel = ui.panel; if (!panel) return false; - window.TarinaiTooltips?.hide?.(); + window.TarinaiTooltips.hide(); if (tab === "selected" && (!world.selected || world.selected.dead || !world.tarinai.includes(world.selected))) { selectTool("observe"); uiCache.showSelectedEmpty = true; @@ -110,8 +106,6 @@ function bindUI() { return true; } - window.scrollPanelToTab = scrollPanelToTab; - const activatePanelQuickTab = (e) => { const btn = e.target.closest("[data-panel-tab]"); if (!btn) return; @@ -164,7 +158,7 @@ function bindUI() { } function applyDesktopInitialPanelState() { - const touchFirst = Boolean(window.TarinaiInputMode?.classify?.().touchFirst || document.body.classList.contains("touch-mobile-ui")); + const touchFirst = Boolean(window.TarinaiInputMode.classify().touchFirst || document.body.classList.contains("touch-mobile-ui")); const desktop = !touchFirst && isDesktopLayout(); if (!desktop || uiCache.desktopInitialPanelStateApplied) return; uiCache.desktopInitialPanelStateApplied = true; @@ -173,7 +167,7 @@ function bindUI() { } function applyMobileInitialPanelState() { - const touchFirst = Boolean(window.TarinaiInputMode?.classify?.().touchFirst || document.body.classList.contains("touch-mobile-ui")); + const touchFirst = Boolean(window.TarinaiInputMode.classify().touchFirst || document.body.classList.contains("touch-mobile-ui")); if (!touchFirst || uiCache.mobileInitialPanelStateApplied) return; uiCache.mobileInitialPanelStateApplied = true; setCollapsibleCardState(document.querySelector(".colony-card"), true); @@ -191,7 +185,7 @@ function bindUI() { const btn = ui.backToGameBtn; if (!btn) return; const stage = document.querySelector(".stage-wrap"); - const touchFirst = Boolean(window.TarinaiInputMode?.classification?.touchFirst || document.body.classList.contains("touch-mobile-ui") || window.innerWidth <= 980); + const touchFirst = Boolean(window.TarinaiInputMode.classification?.touchFirst || document.body.classList.contains("touch-mobile-ui") || window.innerWidth <= 980); if (!touchFirst || !stage) { btn.hidden = true; return; } const rect = stage.getBoundingClientRect(); const visible = rect.bottom > 42 && rect.top < Math.min(window.innerHeight * 0.72, window.innerHeight - 90); @@ -206,7 +200,7 @@ function bindUI() { window.addEventListener("resize", updateBackToGameButton, { passive: true }); updateBackToGameButton(); - window.TarinaiEvents?.on?.("selection:changed", () => { + window.TarinaiEvents.on("selection:changed", () => { if (world?._scrollSelectedDataTopOnNextSelection && isDesktopLayout() && world.selected && !world.selected.dead) { world._scrollSelectedDataTopOnNextSelection = false; window.setTimeout(() => { @@ -216,8 +210,8 @@ function bindUI() { return; } world && (world._scrollSelectedDataTopOnNextSelection = false); - const touchFirst = Boolean(window.TarinaiInputMode?.classification?.touchFirst || document.body.classList.contains("touch-mobile-ui") || window.innerWidth <= 980); - const mode = window.TarinaiInputMode?.currentMode || uiCache.mobileInputMode || "auto"; + const touchFirst = Boolean(window.TarinaiInputMode.classification?.touchFirst || document.body.classList.contains("touch-mobile-ui") || window.innerWidth <= 980); + const mode = window.TarinaiInputMode.currentMode || uiCache.mobileInputMode || "auto"; if (!touchFirst || mode !== "auto" || world.tool !== "observe" || !world.selected || world.selected.dead) return; window.setTimeout(() => { renderSelected?.(); @@ -226,15 +220,15 @@ function bindUI() { }); function activeRotatableToolType() { - const type = typeof toolItemType === "function" ? toolItemType(world?.tool || "") : ""; - return type && typeof isRotatableItemType === "function" && isRotatableItemType(type) ? type : ""; + const type = toolItemType(world?.tool || ""); + return type && isRotatableItemType(type) ? type : ""; } function syncMobileRotateControls() { const controls = ui.mobileRotateControls; if (!controls) return; const show = isTouchMobileLayout() - && (window.TarinaiInputMode?.currentMode || uiCache.mobileInputMode || "auto") !== "camera" + && (window.TarinaiInputMode.currentMode || uiCache.mobileInputMode || "auto") !== "camera" && Boolean(activeRotatableToolType()); controls.hidden = !show; } @@ -258,7 +252,7 @@ function bindUI() { e.preventDefault(); rotateMobileTool(btn.dataset.mobileRotate || "right"); }); - window.TarinaiEvents?.on?.("tool:selected", syncMobileRotateControls); + window.TarinaiEvents.on("tool:selected", syncMobileRotateControls); window.addEventListener("resize", syncMobileRotateControls, { passive: true }); syncMobileRotateControls(); @@ -281,7 +275,7 @@ function bindUI() { syncMobileRotateControls(); } else if (key === "r") { e.preventDefault(); - world.setToolAngle?.(type, typeof defaultItemAngle === "function" ? defaultItemAngle(type) : 0); + world.setToolAngle?.(type, defaultItemAngle(type)); render(); syncMobileRotateControls(); } @@ -302,7 +296,6 @@ function bindUI() { uiCache.chartMode = btn.dataset.chart || "population"; for (const b of ui.colonyChartTabs.querySelectorAll("button")) b.classList.toggle("active", b === btn); uiCache.lastChartDraw = ""; - uiCache.lastChartDrawAt = 0; renderStats(); }); ui.logPushEnabled?.addEventListener("change", () => { @@ -352,7 +345,7 @@ function bindUI() { const id = node.getAttribute("data-family-tarinai-id"); const t = world.tarinai.find(x => (x.id === id || x.familyKey === id) && !x.dead); if (!t) { showToast("\u305d\u306e\u500b\u4f53\u306f\u3082\u3046\u9078\u629e\u3067\u304d\u307e\u305b\u3093\u3002"); return; } - window.TarinaiCommands?.dispatch?.(world, { type: "selection.focus", target: t, options: { pulse: 1.15 } }); + window.TarinaiCommands.dispatch(world, { type: "selection.focus", target: t, options: { pulse: 1.15 } }); selectTool("observe"); renderStats(); showToast(`${t.name}\u3092\u9078\u629e\u3057\u307e\u3057\u305f\u3002`); @@ -388,7 +381,7 @@ function bindUI() { } const btn = e.target.closest("[data-selected-action=\"favorite\"]"); if (!btn || !world.selected || world.selected.dead) return; - const favResult = window.TarinaiCommands?.dispatch?.(world, { type: "selection.favorite.toggle" }); + const favResult = window.TarinaiCommands.dispatch(world, { type: "selection.favorite.toggle" }); if (!favResult?.ok) return; uiCache.selectedSnapshot = ""; renderSelected(); @@ -407,7 +400,7 @@ function bindUI() { if (e.key === "Escape") { e.preventDefault(); input.value = world.selected?.name || ""; input.blur(); } }); ui.selectedCloseBtn?.addEventListener("click", () => { - window.TarinaiCommands?.dispatch?.(world, { type: "selection.clear" }); + window.TarinaiCommands.dispatch(world, { type: "selection.clear" }); uiCache.showSelectedEmpty = false; uiCache.selectedSnapshot = ""; uiCache.selectedEmpty = false; @@ -417,18 +410,17 @@ function bindUI() { ui.pauseBtn?.addEventListener("click", () => { audio.uiClick?.(); - const pauseResult = window.TarinaiCommands?.dispatch?.(world, { type: "simulation.paused.toggle" }); + const pauseResult = window.TarinaiCommands.dispatch(world, { type: "simulation.paused.toggle" }); const paused = pauseResult?.ok ? pauseResult.paused : world.paused; const archiveStale = uiCache.archiveFamilyVersion !== (world.familyVersion || 0); - if (archiveAutoUpdateEnabled() && paused && (uiCache.archivePendingWhileRunning || archiveStale)) { - uiCache.archivePendingWhileRunning = false; + if (archiveAutoUpdateEnabled() && paused && archiveStale) { scheduleArchiveWindowRender(); } syncTopButtons(); }); ui.speedBtn?.addEventListener("click", () => { audio.uiClick?.(); - window.TarinaiCommands?.dispatch?.(world, { type: "simulation.speed.cycle", speeds: [1, 2, 4] }); + window.TarinaiCommands.dispatch(world, { type: "simulation.speed.cycle", speeds: [1, 2, 4] }); syncTopButtons(); }); ui.ecologyBtn?.addEventListener("click", () => { audio.uiClick?.(); openEcologyDialog(); }); @@ -509,7 +501,7 @@ function bindUI() { const tool = btn.dataset.tool; audio.uiClick?.(); if (tool === "undo" || tool === "redo") { - const result = window.TarinaiCommands?.dispatch?.(world, { type: tool === "undo" ? "history.undo" : "history.redo" }); + const result = window.TarinaiCommands.dispatch(world, { type: tool === "undo" ? "history.undo" : "history.redo" }); showToast(result?.ok ? (tool === "undo" ? "\u623b\u3057\u307e\u3057\u305f\u3002" : "\u9032\u3081\u307e\u3057\u305f\u3002") : (tool === "undo" ? "\u623b\u305b\u308b\u64cd\u4f5c\u304c\u3042\u308a\u307e\u305b\u3093\u3002" : "\u9032\u3081\u308b\u64cd\u4f5c\u304c\u3042\u308a\u307e\u305b\u3093\u3002")); renderStats(); return; @@ -576,10 +568,10 @@ function bindUI() { chainScroll(ui.archiveContent); transferEdgeScroll(ui.log, rightPanel); - const inputContext = window.TarinaiUIInputShared?.createInputContext?.({ canvas, world, ui, uiCache }); + const inputContext = window.TarinaiUIInputShared.createInputContext({ canvas, world, ui, uiCache }); if (!inputContext) throw new Error("Tarinai UI input context is not available"); - window.TarinaiTouchInput?.bindTouchInput?.(inputContext); - window.TarinaiMouseInput?.bindMouseInput?.(inputContext); + window.TarinaiTouchInput.bindTouchInput(inputContext); + window.TarinaiMouseInput.bindMouseInput(inputContext); window.addEventListener("keydown", (e) => { if (e.key !== "Escape") return; diff --git a/js/ui_charts.js b/js/ui_charts.js index 2393170..44e5aca 100644 --- a/js/ui_charts.js +++ b/js/ui_charts.js @@ -11,7 +11,7 @@ function bindColonyMoodTooltip() { if (!target || target.dataset.colonyMoodTipBound === "1") return; target.dataset.colonyMoodTipBound = "1"; target.tabIndex = target.tabIndex >= 0 ? target.tabIndex : 0; - window.TarinaiTooltips?.bind?.(target, colonyMoodTip); + window.TarinaiTooltips.bind(target, colonyMoodTip); } function renderStats() { @@ -31,8 +31,8 @@ function renderStats() { const colonyMood = world.evaluateColonyMood?.() || world.colonyMood || { label: "\u5b89\u5b9a" }; setTextIfChanged(ui.statColonyMood, "colonyMood", colonyMood?.label || "\u5b89\u5b9a"); bindColonyMoodTooltip(); - window.TarinaiTooltips?.refresh?.(ui.statColonyMood?.closest?.(".stat") || ui.statColonyMood); - window.TarinaiGroundUI?.update?.(world); + window.TarinaiTooltips.refresh(ui.statColonyMood?.closest?.(".stat") || ui.statColonyMood); + window.TarinaiGroundUI.update(world); updateColonyHistory(values); drawColonyChart(values); renderSelected(); @@ -114,7 +114,6 @@ function chartRows(values) { function drawColonyChart(values) { const chart = ui.colonyChart; if (!chart) return; - const now = performance.now(); let mode = uiCache.chartMode || "population"; if (!["population", "life", "objects"].includes(mode)) { mode = "population"; @@ -128,7 +127,6 @@ function drawColonyChart(values) { const snapshot = `${mode}:${rows.length}:${seriesSnapshot}:${chart.clientWidth}x${chart.clientHeight}`; if (uiCache.lastChartDraw === snapshot) return; uiCache.lastChartDraw = snapshot; - uiCache.lastChartDrawAt = now; const cssW = Math.max(280, Math.floor(chart.clientWidth || chart.offsetWidth || 320)); const cssH = Math.max(160, Math.floor(chart.clientHeight || chart.offsetHeight || 188)); const pad = { l: 30, r: 12, t: 12, b: 24 }; diff --git a/js/ui_family_render.js b/js/ui_family_render.js index 97d8c60..917ffbf 100644 --- a/js/ui_family_render.js +++ b/js/ui_family_render.js @@ -219,7 +219,6 @@ function renderArchive() { uiCache.archivePendingDirtyAfterRun = false; return; } - const renderStarted = performance.now(); lineageSetArchiveStaleStatus(false); if (uiCache.archiveRenderTimer) { clearTimeout(uiCache.archiveRenderTimer); @@ -227,9 +226,7 @@ function renderArchive() { } uiCache.archiveVersion = version; uiCache.archiveFamilyVersion = world.familyVersion || 0; - uiCache.archiveLastScheduleFamilyVersion = world.familyVersion || 0; uiCache.archiveLastRenderedAt = performance.now(); - uiCache.lastArchiveWindow = ""; if (!uiCache.archiveHtmlCache) uiCache.archiveHtmlCache = new Map(); const activeCacheKeys = new Set(); const token = `${version}:${Math.random().toString(36).slice(2)}`; @@ -245,7 +242,6 @@ function renderArchive() { return; } const splitComponents = components.flatMap(lineageSplitByActualEdges).filter(c => c.length); - uiCache.archiveRows = splitComponents; if (!splitComponents.length) { ui.archiveContent.innerHTML = `
\u751f\u304d\u3066\u3044\u308b\u69cb\u6210\u54e1\u3092\u6301\u3064\u5bb6\u7cfb\u306f\u307e\u3060\u3042\u308a\u307e\u305b\u3093\u3002
`; lineageRestoreArchiveScroll(scrollState); @@ -288,12 +284,6 @@ function renderArchive() { const renderDirtiedAgain = uiCache.archivePendingDirtyAfterRun || (world.familyVersion || 0) !== familyVersion; uiCache.archiveRenderRunning = false; world.familyTreeDirty = renderDirtiedAgain; - if (uiCache.archiveDiagnostics) { - uiCache.archiveDiagnostics.renders = (uiCache.archiveDiagnostics.renders || 0) + 1; - uiCache.archiveDiagnostics.lastMs = Math.round((performance.now() - renderStarted) * 10) / 10; - uiCache.archiveDiagnostics.lastFamilies = splitComponents.length; - uiCache.archiveDiagnostics.lastNodes = splitComponents.reduce((sum, component) => sum + component.length, 0); - } if (renderDirtiedAgain) { uiCache.archivePendingDirtyAfterRun = false; scheduleArchiveWindowRender(); @@ -321,54 +311,13 @@ function resetArchiveRenderState() { uiCache.archiveRenderRunning = false; uiCache.archiveScheduled = false; uiCache.archivePendingOffscreen = false; - uiCache.archivePendingWhileRunning = false; uiCache.archivePendingDirtyAfterRun = false; uiCache.archiveLastHiddenCheckAt = -Infinity; - uiCache.archiveLastScheduleFamilyVersion = null; uiCache.archiveVersion = ""; uiCache.archiveFamilyVersion = null; - uiCache.archiveRelationSignature = ""; - uiCache.archiveRows = []; - uiCache.lastArchiveWindow = ""; uiCache.archiveHtmlCache?.clear?.(); } -function validateFamilyTree() { - const issues = world.validateFamily ? [...world.validateFamily()] : []; - const rendered = new Map(); - document.querySelectorAll("[data-family-tarinai-id]").forEach(node => { - const id = node.getAttribute("data-family-tarinai-id"); - rendered.set(id, (rendered.get(id) || 0) + 1); - }); - for (const [id, count] of rendered.entries()) { - if (count > 1) issues.push({ type: "duplicate-render-node", id, count }); - } - for (const component of uiCache.archiveRows || []) { - for (const n of component || []) { - if (n?.id && !rendered.has(n.id)) issues.push({ type: "missing-render-node", id: n.id }); - } - } - document.querySelectorAll(".lineage-family-tree[data-lineage-child-edges]").forEach((section, index) => { - const expectedEdges = Number(section.getAttribute("data-lineage-child-edges") || 0); - const renderedEdges = Number(section.getAttribute("data-lineage-rendered-child-edges") || 0); - const expectedGroups = Number(section.getAttribute("data-lineage-child-groups") || 0); - const renderedPaths = Number(section.getAttribute("data-lineage-child-paths") || 0); - const pathCount = section.querySelectorAll(".lineage-links .child-link").length; - if (renderedEdges !== expectedEdges) { - issues.push({ type: "render-child-edge-count-mismatch", familyIndex: index, expectedEdges, renderedEdges }); - } - if (expectedEdges > 0 && pathCount < 1) { - issues.push({ type: "missing-render-child-link", familyIndex: index, expectedEdges, pathCount }); - } - if (expectedGroups > 0 && pathCount < expectedGroups) { - issues.push({ type: "missing-render-child-link-group", familyIndex: index, expectedGroups, pathCount }); - } - if (renderedPaths !== pathCount) { - issues.push({ type: "render-child-path-count-mismatch", familyIndex: index, renderedPaths, pathCount }); - } - }); - return issues; -} function scheduleArchiveWindowRender() { if (typeof archiveAutoUpdateEnabled === "function" && !archiveAutoUpdateEnabled()) { uiCache.archiveScheduled = false; @@ -404,6 +353,4 @@ function schedulePendingArchiveRender() { scheduleArchiveWindowRender(); } -window.validateFamilyTree = validateFamilyTree; -window.familyTreeDiagnostics = () => ({ ...(uiCache.archiveDiagnostics || {}) }); window.resetArchiveRenderState = resetArchiveRenderState; diff --git a/js/ui_ground.js b/js/ui_ground.js index 041c6b1..5f33404 100644 --- a/js/ui_ground.js +++ b/js/ui_ground.js @@ -16,13 +16,12 @@ function groundDef(worldRef = null) { const worldObj = activeWorld(worldRef); const id = worldObj?.groundType || "soil"; - return global.TarinaiGround?.definition?.(id) || (typeof GROUND_TYPES !== "undefined" ? GROUND_TYPES[id] || GROUND_TYPES.soil : { id: "soil", label: "\u7802", description: "\u666e\u901a\u306e\u5730\u9762\u3002" }); + return global.TarinaiGround.definition(id); } function groundTip(worldRef = null) { const def = groundDef(worldRef); - if (global.TarinaiGround?.tooltipText) return global.TarinaiGround.tooltipText(def.id); - return `${def.label || "\u5730\u9762"}\n${def.description || "\u30af\u30ea\u30c3\u30af\u3067\u5207\u308a\u66ff\u3048"}`; + return global.TarinaiGround.tooltipText(def.id); } function updateGroundUI(worldRef = null) { @@ -37,7 +36,7 @@ btnEl.dataset.tip = desc; btnEl.removeAttribute("title"); btnEl.setAttribute("aria-label", desc ? `\u5730\u9762\u3092\u5207\u308a\u66ff\u3048\u308b\u3002\u73fe\u5728\u306f${label}\u3002${desc}` : `\u5730\u9762\u3092\u5207\u308a\u66ff\u3048\u308b\u3002\u73fe\u5728\u306f${label}`); - global.TarinaiTooltips?.refresh?.(btnEl); + global.TarinaiTooltips.refresh(btnEl); } } @@ -55,15 +54,14 @@ } global.audio?.uiClick?.(); currentWorld.cycleGroundType(); - global.TarinaiRender?.invalidateRenderCaches?.("ground-cycle"); + global.TarinaiRender.invalidateRenderCaches("ground-cycle"); if (global.uiCache) { - global.uiCache.lastStatsGroundType = ""; if (global.uiCache.stats) global.uiCache.stats.groundType = ""; global.uiCache.lastChartDraw = ""; } updateGroundUI(currentWorld); - global.renderStats?.(); - global.render?.(); + global.renderStats(); + global.render(); } function isGroundToggleTarget(target) { @@ -105,15 +103,12 @@ cycleGroundFromEvent(event, activeWorld(worldObj)); }, { capture: true, passive: false }); } - global.TarinaiTooltips?.bind?.(btn, () => groundTip(activeWorld(worldObj))); + global.TarinaiTooltips.bind(btn, () => groundTip(activeWorld(worldObj))); updateGroundUI(activeWorld(worldObj)); } global.TarinaiGroundUI = Object.freeze({ - bind: bindGroundUI, update: updateGroundUI, - tip: groundTip, - cycle: cycleGroundFromEvent, }); global.bindGroundUI = bindGroundUI; })(window); diff --git a/js/ui_helpers.js b/js/ui_helpers.js index af3e660..51387ba 100644 --- a/js/ui_helpers.js +++ b/js/ui_helpers.js @@ -5,29 +5,5 @@ return String(value ?? "").replace(/[&<>"']/g, ch => ({ "&": "&", "<": "<", ">": ">", "\"": """, "'": "'" }[ch] || ch)); } - function ensureStyle(id, cssText = "") { - if (!id) return null; - let style = document.getElementById(id); - if (style) return style; - style = document.createElement("style"); - style.id = id; - style.textContent = cssText; - document.head.appendChild(style); - return style; - } - - function createPanel({ id, className = "", html = "", after = null, parent = document.body } = {}) { - if (!id) return null; - let panel = document.getElementById(id); - if (panel) return panel; - panel = document.createElement("section"); - panel.id = id; - if (className) panel.className = className; - panel.innerHTML = html; - if (after?.parentNode) after.parentNode.insertBefore(panel, after.nextSibling); - else parent.appendChild(panel); - return panel; - } - - global.TarinaiUIHelpers = { htmlEscape, ensureStyle, createPanel }; + global.TarinaiUIHelpers = { htmlEscape }; })(typeof window !== "undefined" ? window : globalThis); diff --git a/js/ui_input_mouse.js b/js/ui_input_mouse.js index 9cf6709..eb34f41 100644 --- a/js/ui_input_mouse.js +++ b/js/ui_input_mouse.js @@ -4,7 +4,7 @@ function bindMouseInput(ctx) { if (!ctx?.canvas) return false; const { canvas, world, uiCache } = ctx; - const actions = global.TarinaiPointerActionSystem?.create?.(ctx); + const actions = global.TarinaiPointerActionSystem.create(ctx); canvas.addEventListener("contextmenu", (e) => e.preventDefault()); canvas.addEventListener("mousedown", (e) => { @@ -45,7 +45,6 @@ world.pointer.motion = 0; world.hoverGrabTarget = null; world.hoverDeleteTarget = null; - world.hoverShootTarget = null; }); return true; } diff --git a/js/ui_input_shared.js b/js/ui_input_shared.js index 7ea5049..3c80ec5 100644 --- a/js/ui_input_shared.js +++ b/js/ui_input_shared.js @@ -53,7 +53,7 @@ root.dataset.lastActivateAt = String(now); e.preventDefault?.(); e.stopPropagation?.(); - window.TarinaiTooltips?.hide?.(); + window.TarinaiTooltips.hide(); audio.uiClick?.(); setMobileInputMode(btn.dataset.mobileMode || "auto"); showToast(`\u30b9\u30de\u30db\u64cd\u4f5c: ${btn.textContent || btn.dataset.mobileMode}`); @@ -92,7 +92,6 @@ } function screenToWorldEvent(e) { - if (typeof screenToWorld === "function") return screenToWorld(e); return screenToWorldClient(e.clientX, e.clientY); } @@ -148,26 +147,26 @@ function syncGrabbedPhysicsItem(target, reason = "pinch-move") { if (!target || target.dead) return false; - const pb = (typeof window !== "undefined" ? window : globalThis).TarinaiPhysicsBodySystem; - if (!pb?.isBodyType?.(target.type)) return false; - pb.syncPoseFromItem?.(target); - pb.setScalar?.(target, "awakeUntil", Math.max(Number(pb.scalar?.(target, "awakeUntil", 0) || 0), (world.time || 0) + 0.65), reason); - pb.markBodyChanged?.(target, reason); - (typeof window !== "undefined" ? window : globalThis).TarinaiMechanicalSystem?.invalidateGeometry?.(target); + const pb = global.TarinaiPhysicsBodySystem; + if (!pb.isBodyType(target.type)) return false; + pb.syncPoseFromItem(target); + pb.setScalar(target, "awakeUntil", Math.max(Number(pb.scalar(target, "awakeUntil", 0) || 0), (world.time || 0) + 0.65), reason); + pb.markBodyChanged(target, reason); + global.TarinaiMechanicalSystem.invalidateGeometry(target); return true; } function prepareGrabTarget(target) { if (uiCache.grabKind === "tarinai") { const lodged = target.currentLodgedPin?.() || (world.items || []).find(it => it && isPinType(it.type) && it.pinState === "lodged" && it.pinTargetId === target.id); - if (lodged?.detachPushpin) lodged.detachPushpin(world, "pinch"); + if (lodged) global.TarinaiItemDynamicPinSystem.detach(lodged, world, "pinch"); target.sleeping = false; if (target.state === "sleep" || target.state === "seek_bed") target.goIdle?.("\u3064\u307e\u307e\u308c\u3066\u3044\u308b"); target.surpriseTimer = Math.max(target.surpriseTimer || 0, 0.18); target.thought = "\u3064\u307e\u307e\u308c\u3066\u3044\u308b"; target.pinchThoughtTimer = Math.max(target.pinchThoughtTimer || 0, 1.2); } else { - if (isPinType(target.type) && target.pinState === "lodged" && target.detachPushpin) target.detachPushpin(world, "pinch"); + if (isPinType(target.type) && target.pinState === "lodged") global.TarinaiItemDynamicPinSystem.detach(target, world, "pinch"); target.dropTimer = 0; target.dropMax = 0; target.dropImpactDone = true; @@ -180,7 +179,7 @@ if (options.notifyNoTarget) showToast("\u3064\u307e\u3081\u308b\u5bfe\u8c61\u304c\u3042\u308a\u307e\u305b\u3093\u3002"); return false; } - global.TarinaiHistory?.capture?.(world, "pinch"); + global.TarinaiHistory.capture(world, "pinch"); audio.grab?.(); uiCache.grabbing = true; uiCache.grabMoved = false; @@ -196,12 +195,9 @@ uiCache.grabStartY = clientY || 0; target.playerHeld = true; target._heldByPlayer = true; - target._playerGrabStartedAt = world.time || 0; if (uiCache.grabKind === "item") syncGrabbedPhysicsItem(target, "pinch-start"); if (target.type === "duplicator") { target.duplicatorLoadSuppressedUntil = Math.max(target.duplicatorLoadSuppressedUntil || 0, (world.time || 0) + 1.0); - target._duplicatorCandidateKey = ""; - target._duplicatorCandidateSince = 0; } prepareGrabTarget(target); if (options.touchMode) uiCache.touchMode = options.touchMode; @@ -297,11 +293,8 @@ if (uiCache.grabKind === "item") syncGrabbedPhysicsItem(target, "pinch-release"); target.playerHeld = false; target._heldByPlayer = false; - target._lastPlayerReleasedAt = world.time || 0; if (target.type === "duplicator") { target.duplicatorLoadSuppressedUntil = Math.max(target.duplicatorLoadSuppressedUntil || 0, (world.time || 0) + 1.2); - target._duplicatorCandidateKey = ""; - target._duplicatorCandidateSince = 0; } } audio.drop?.(); @@ -319,7 +312,7 @@ } function beginHose(p, clientX, clientY, options = {}) { - global.TarinaiHistory?.capture?.(world, "water-hose"); + global.TarinaiHistory.capture(world, "water-hose"); audio.waterHose?.(); uiCache.hosing = true; uiCache.hoseMoved = false; @@ -366,22 +359,11 @@ return true; } - function shootAimPoint() { - return world.shootAimPoint?.() || { x: world.pointer?.x || 0, y: world.pointer?.y || 0, inside: Boolean(world.pointer?.inside) }; - } - function updateShootHoverTarget(point = null) { - // \u5c04\u6483\u30c4\u30fc\u30eb\u306f\u7167\u6e96\u3060\u3051\u3092\u8868\u793a\u3057\u3001\u5bfe\u8c61\u30cf\u30a4\u30e9\u30a4\u30c8\u306f\u51fa\u3055\u306a\u3044\u3002 - if (world.hoverShootTarget) { - world.hoverShootTarget = null; - world.drawListDirty = true; - } - return null; - } function beginShoot(p, clientX, clientY, options = {}) { if (!p?.inside) return false; - global.TarinaiHistory?.capture?.(world, "shoot"); + global.TarinaiHistory.capture(world, "shoot"); uiCache.shooting = true; uiCache.shootMoved = false; uiCache.shootSuppressClick = true; @@ -396,7 +378,6 @@ world.setShootCursorFromPointer?.(p); uiCache.shootLastRawWorldX = p.x; uiCache.shootLastRawWorldY = p.y; - updateShootHoverTarget(); uiCache.shootFired = Boolean(world.fireShootTool?.()) || Boolean(uiCache.shootFired); if (options.touchMode) uiCache.touchMode = options.touchMode; if (uiCache.shootHoldTimer) clearInterval(uiCache.shootHoldTimer); @@ -429,7 +410,6 @@ uiCache.shootMoved = true; if (options.touch) uiCache.touchMoved = true; } - updateShootHoverTarget(); canvas.style.cursor = "none"; render(); return true; @@ -444,7 +424,6 @@ } if (uiCache.shootFired) audio.boltAction?.(); uiCache.shootFired = false; - updateShootHoverTarget(); canvas.style.cursor = cursorForTool(world.tool); renderStats(); return true; @@ -452,13 +431,11 @@ function beginAreaDelete(p, clientX, clientY, options = {}) { - global.TarinaiHistory?.capture?.(world, "area-delete"); + global.TarinaiHistory.capture(world, "area-delete"); uiCache.areaDeleting = true; uiCache.areaDeleteMoved = false; uiCache.areaDeleteStartX = clientX; uiCache.areaDeleteStartY = clientY; - uiCache.areaDeleteLastWorldX = p.x; - uiCache.areaDeleteLastWorldY = p.y; world._areaDeleteDragRemoved = 0; if (p.inside) world.deleteItemsInArea?.(p.x, p.y, { silentEmpty: true, silentLog: true, drag: true }); if (options.touchMode) uiCache.touchMode = options.touchMode; @@ -550,8 +527,7 @@ world.shootPointerRawX = p.x; world.shootPointerRawY = p.y; } - updateShootHoverTarget(); - canvas.style.cursor = "none"; + canvas.style.cursor = "none"; } else if (world.tool === "pinch" && p.inside && !uiCache.grabbing && !uiCache.panning && !uiCache.hosing && !uiCache.areaDeleting && !uiCache.shooting) { const target = world.findGrabTargetAt ? world.findGrabTargetAt(p.x, p.y) : null; if (world.hoverGrabTarget !== target) { @@ -577,10 +553,6 @@ world.hoverDeleteTarget = null; world.drawListDirty = true; } - if (world.hoverShootTarget) { - world.hoverShootTarget = null; - world.drawListDirty = true; - } } } @@ -602,7 +574,6 @@ if (world) { world.hoverGrabTarget = null; world.hoverDeleteTarget = null; - world.hoverShootTarget = null; } } diff --git a/js/ui_input_touch.js b/js/ui_input_touch.js index 5980275..c6809da 100644 --- a/js/ui_input_touch.js +++ b/js/ui_input_touch.js @@ -4,7 +4,7 @@ function bindTouchInput(ctx) { if (!ctx?.canvas) return false; const { canvas, world, uiCache } = ctx; - const actions = global.TarinaiPointerActionSystem?.create?.(ctx); + const actions = global.TarinaiPointerActionSystem.create(ctx); ctx.bindMobileModeControls(); const isMobileAutoScrollMode = () => { const mode = ctx.mobileInputMode?.() || "auto"; diff --git a/js/ui_layout_dialogs.js b/js/ui_layout_dialogs.js index aade08d..0d94f99 100644 --- a/js/ui_layout_dialogs.js +++ b/js/ui_layout_dialogs.js @@ -2,7 +2,7 @@ function resizeCanvas() { const rect = canvas.getBoundingClientRect(); - const perfScale = window.TarinaiPerf?.dprScale?.() || 1; + const perfScale = window.TarinaiPerf.dprScale(); const dpr = Math.max(0.5, Math.min((window.devicePixelRatio || 1) * perfScale, 2)); const nextW = Math.max(1, Math.floor(rect.width * dpr)); const nextH = Math.max(1, Math.floor(rect.height * dpr)); @@ -20,11 +20,12 @@ function resizeCanvas() { function applyFieldLayout(fieldType = world.fieldType || "garden") { const field = FIELD_TYPES?.[fieldType] || FIELD_TYPES.garden; + const layout = FIELD_LAYOUT; const root = document.documentElement; - root.style.setProperty("--field-height", field.height); - root.style.setProperty("--field-min-height", field.minHeight || field.height); - root.style.setProperty("--field-mobile-height", field.mobileHeight || field.height); - root.style.setProperty("--field-mobile-min-height", field.mobileMinHeight || field.mobileHeight || "460px"); + root.style.setProperty("--field-height", layout.height); + root.style.setProperty("--field-min-height", layout.minHeight); + root.style.setProperty("--field-mobile-height", layout.mobileHeight); + root.style.setProperty("--field-mobile-min-height", layout.mobileMinHeight); world.fieldType = field.id; const rect = canvas.getBoundingClientRect(); world.setViewportSize(rect.width, rect.height); @@ -87,17 +88,10 @@ function resetWithField(fieldType) { render(); } -function screenToWorld(e) { - const rect = canvas.getBoundingClientRect(); - const touch = e.touches ? e.touches[0] : e; - const sx = touch.clientX - rect.left; - const sy = touch.clientY - rect.top; - return world.screenToWorld ? world.screenToWorld(sx, sy) : { x: sx, y: sy }; -} function resizeFieldFromWheel(e) { - const itemType = typeof toolItemType === "function" ? toolItemType(world?.tool || "") : ""; - if ((e.shiftKey || e.altKey) && itemType && typeof isRotatableItemType === "function" && isRotatableItemType(itemType) && world?.rotateToolAngle) { + const itemType = toolItemType(world?.tool || ""); + if ((e.shiftKey || e.altKey) && itemType && isRotatableItemType(itemType) && world?.rotateToolAngle) { e.preventDefault(); const step = (e.altKey ? 1 : 5) * Math.PI / 180; world.rotateToolAngle(itemType, (e.deltaY > 0 ? 1 : -1) * step); @@ -122,7 +116,7 @@ function resizeFieldFromWheel(e) { function selectTool(tool) { const wasCopyArmed = world.tool === "copy" && tool === "copy" && Boolean(world.copyBuffer); - const result = window.TarinaiCommands?.dispatch?.(world, { type: "tool.select", toolId: tool }); + const result = window.TarinaiCommands.dispatch(world, { type: "tool.select", toolId: tool }); const activeTool = result?.toolId || world.tool || tool; if (activeTool !== "rope" && activeTool !== "rod") world.pendingLinkEndpoint = null; if (activeTool !== "copy") world.copyBuffer = null; @@ -134,7 +128,7 @@ function selectTool(tool) { btn.classList.toggle("selected", btn.dataset.tool === activeTool); } syncToolSizeBadges(); - canvas.style.cursor = window.TarinaiUIInputShared?.cursorForTool?.(activeTool) || (activeTool === "observe" ? "default" : (activeTool === "pinch" ? "grab" : (activeTool === "poke" || activeTool === "delete" ? "pointer" : "crosshair"))); + canvas.style.cursor = window.TarinaiUIInputShared.cursorForTool(activeTool) || (activeTool === "observe" ? "default" : (activeTool === "pinch" ? "grab" : (activeTool === "poke" || activeTool === "delete" ? "pointer" : "crosshair"))); } function showToast(text) { diff --git a/js/ui_log.js b/js/ui_log.js index d69a775..9df3cd6 100644 --- a/js/ui_log.js +++ b/js/ui_log.js @@ -92,7 +92,7 @@ function shouldShowInEventLog(entry = {}) { function renderLog(logs) { ui.log.innerHTML = ""; const frag = document.createDocumentFragment(); - const touchFirst = Boolean(window.TarinaiInputMode?.classification?.touchFirst || document.body.classList.contains("touch-mobile-ui") || window.innerWidth <= 980); + const touchFirst = Boolean(window.TarinaiInputMode.classification?.touchFirst || document.body.classList.contains("touch-mobile-ui") || window.innerWidth <= 980); const limit = touchFirst ? 34 : 80; const visibleLogs = Array.isArray(logs) ? logs.map(raw => typeof raw === "string" ? { time: world.time, text: raw, kind: "note" } : raw).filter(shouldShowInEventLog) @@ -182,7 +182,7 @@ function relationListHtml(t, kind = "friend") { function focusTarinaiById(id) { const t = world?.tarinai?.find(o => o && !o.dead && (o.id === id || o.familyKey === id)); if (!t) { showToast("\u305d\u306e\u500b\u4f53\u306f\u3082\u3046\u9078\u629e\u3067\u304d\u307e\u305b\u3093\u3002"); return false; } - window.TarinaiCommands?.dispatch?.(world, { type: "selection.focus", target: t, options: { pulse: 1.25 } }); + window.TarinaiCommands.dispatch(world, { type: "selection.focus", target: t, options: { pulse: 1.25 } }); selectTool("observe"); uiCache.selectedSnapshot = ""; renderSelected(); diff --git a/js/ui_pointer_action_system.js b/js/ui_pointer_action_system.js index d1e97ec..6efd849 100644 --- a/js/ui_pointer_action_system.js +++ b/js/ui_pointer_action_system.js @@ -92,7 +92,7 @@ function dispatchTapAt(p) { if (!p?.inside) return false; - global.TarinaiCommands?.dispatch?.(world, { type: "pointer.primary", x: p.x, y: p.y }); + global.TarinaiCommands.dispatch(world, { type: "pointer.primary", x: p.x, y: p.y }); global.renderStats?.(); return true; } diff --git a/js/ui_selected.js b/js/ui_selected.js index 9afb4a0..84e397a 100644 --- a/js/ui_selected.js +++ b/js/ui_selected.js @@ -174,10 +174,6 @@ function selectedInventoryHtml(t) { return `
\u6240\u6301\u54c1${entries.map(v => `${escapeHtml(v)}`).join("")}
`; } -function selectedStateText(t) { - return window.TEXT_CATALOG?.stateLabel?.(t?.state, t) || stateLabel(t?.state); -} - function renderSelected() { const t = world.selected; if (!t || t.dead || !world.tarinai.includes(t)) { @@ -209,7 +205,7 @@ function renderSelected() { ].join(","); const itemEffectSummary = t.activeItemEffectSummary ? t.activeItemEffectSummary() : []; const needKeys = typeof TARINAI_NEED_KEYS !== "undefined" ? TARINAI_NEED_KEYS : ["food", "sleep", "health", "safety", "social", "fulfill"]; - const behaviorState = typeof currentTarinaiBehavior === "function" ? currentTarinaiBehavior(t) : t.behavior; + const behaviorState = currentTarinaiBehavior(t); const snapshot = [ t.id, world?.groundType || "soil", t.type, t.name, generationDisplay, t.state, t.thought, targetLabel(t.target), behaviorState?.need || "", behaviorState?.actionId || "", behaviorState?.label || "", behaviorState?.reason || "", behaviorState?.source || "", behaviorState?.text || "", needKeys.map(key => `${key}:${t.needs?.[key] ?? 0}`).join(","), @@ -326,9 +322,7 @@ function isIdleDisplayText(value = "") { function reasonLabel(t) { if (!t) return "\u306a\u3057"; - const behavior = typeof globalThis.behaviorText === "function" - ? globalThis.behaviorText(t) - : String(typeof getTarinaiBehaviorText === "function" ? getTarinaiBehaviorText(t) : (t.behavior?.text || t.behavior?.reason || "")).trim(); + const behavior = behaviorText(t); if (behavior) { const normalized = window.TEXT_CATALOG?.cleanBehaviorText?.(behavior, t.state || "") || behavior; if (isIdleDisplayText(normalized)) return groundAwareIdleText(t); @@ -338,7 +332,7 @@ function reasonLabel(t) { const live = window.TEXT_CATALOG?.reasonLabel?.(t) || ""; const liveStates = new Set(["birth_ritual", "eat", "sleep", "panic", "fight", "intimidate", "ant_attack", "ant_intimidate", "frozen"]); if (live && (liveStates.has(state) || (t.birthRitualTimer || 0) > 0.04 || (t.eatTimer || 0) > 0.04 || (t.fightTimer || 0) > 0.04 || (t.intimidateTimer || 0) > 0.04)) return live; - const behaviorState = typeof currentTarinaiBehavior === "function" ? currentTarinaiBehavior(t) : t?.behavior; + const behaviorState = currentTarinaiBehavior(t); if (behaviorState?.reason || behaviorState?.text) { const raw = behaviorState.reason || behaviorState.text; const normalized = window.TEXT_CATALOG?.cleanBehaviorText?.(raw, behaviorState?.actionId || t.state || "") || raw; diff --git a/js/ui_tools.js b/js/ui_tools.js index f74d7e4..39d740b 100644 --- a/js/ui_tools.js +++ b/js/ui_tools.js @@ -15,7 +15,7 @@ function renderToolPalette() { const section = document.createElement("section"); section.className = `tool-category ${category.themeClass || ""}`.trim(); section.dataset.toolCategory = category.id; - const mobileCompact = Boolean(window.TarinaiInputMode?.classification?.touchFirst || document.body.classList.contains("touch-mobile-ui")); + const mobileCompact = Boolean(window.TarinaiInputMode.classification?.touchFirst || document.body.classList.contains("touch-mobile-ui")); const initiallyCollapsed = mobileCompact && category.id !== "operate"; if (initiallyCollapsed) section.classList.add("collapsed"); const toggle = document.createElement("button"); @@ -95,12 +95,12 @@ function attachToolSpritePreview(btn, def, toolId) { function toolSizeFor(tool) { if (!SCALABLE_TOOLS.has(tool)) return ""; - return typeof normalizedToolSizeFor === "function" ? normalizedToolSizeFor(world, tool) : ((world.toolSizes && world.toolSizes[tool]) || world.toolSize || "medium"); + return normalizedToolSizeFor(world, tool); } function cycleToolSize(tool) { if (!SCALABLE_TOOLS.has(tool)) return false; - const result = window.TarinaiCommands?.dispatch?.(world, { type: "tool.size.cycle", toolId: tool, order: SIZE_ORDER }); + const result = window.TarinaiCommands.dispatch(world, { type: "tool.size.cycle", toolId: tool, order: SIZE_ORDER }); if (!result?.ok) return false; syncToolSizeBadges(); showToast(`\u30b5\u30a4\u30ba: ${SIZE_LABELS[result.size] || result.size}`); diff --git a/js/ui_tooltips.js b/js/ui_tooltips.js index 17c473a..037514e 100644 --- a/js/ui_tooltips.js +++ b/js/ui_tooltips.js @@ -104,22 +104,19 @@ } } - if (!global.__tarinaiTooltipGlobalBound) { - global.__tarinaiTooltipGlobalBound = true; - document.addEventListener("pointerdown", (e) => { - if (!activeTooltipElement) return; - if (activeTooltipElement.contains?.(e.target)) return; - hideTooltip(); - }, { capture: true, passive: true }); - document.addEventListener("touchstart", (e) => { - if (!activeTooltipElement) return; - if (activeTooltipElement.contains?.(e.target)) return; - hideTooltip(); - }, { capture: true, passive: true }); - window.addEventListener("scroll", hideTooltip, { passive: true }); - window.addEventListener("resize", hideTooltip, { passive: true }); - document.addEventListener("visibilitychange", () => { if (document.hidden) hideTooltip(); }); - } + document.addEventListener("pointerdown", (e) => { + if (!activeTooltipElement) return; + if (activeTooltipElement.contains?.(e.target)) return; + hideTooltip(); + }, { capture: true, passive: true }); + document.addEventListener("touchstart", (e) => { + if (!activeTooltipElement) return; + if (activeTooltipElement.contains?.(e.target)) return; + hideTooltip(); + }, { capture: true, passive: true }); + window.addEventListener("scroll", hideTooltip, { passive: true }); + window.addEventListener("resize", hideTooltip, { passive: true }); + document.addEventListener("visibilitychange", () => { if (document.hidden) hideTooltip(); }); global.TarinaiTooltips = Object.freeze({ bind: bindTooltip, diff --git a/js/version.js b/js/version.js index db89cf7..76ca377 100644 --- a/js/version.js +++ b/js/version.js @@ -1,8 +1,8 @@ "use strict"; (function () { - const APP_VERSION = "39.15.63"; - const APP_BUILD = "tarinai-medium-unification-v39-15-60"; + const APP_VERSION = "39.15.65"; + const APP_BUILD = "tarinai-mood-unification-v39-15-65"; const APP_CACHE_NAME = `tarinai-colony-${APP_VERSION}`; const STATIC_VERSION_PARAM = `v=${APP_VERSION}`; diff --git a/js/weather_system.js b/js/weather_system.js index 85fb802..79fc847 100644 --- a/js/weather_system.js +++ b/js/weather_system.js @@ -1,67 +1,66 @@ "use strict"; (function (global) { - const WEATHER_STATES = Object.freeze(["sunny", "cloudy", "light_rain"]); + function chooseNext(current = "sunny") { + const choices = current === "light_rain" + ? ["sunny", "cloudy"] + : ["sunny", "cloudy", "light_rain"]; + return pick(choices); + } - const WeatherSystem = Object.freeze({ - states: WEATHER_STATES, + function resetTimer() { + return rand(CONFIG.weatherChangeMin, CONFIG.weatherChangeMax); + } - chooseNext(current = "sunny") { - const choices = current === "light_rain" - ? ["sunny", "cloudy"] - : ["sunny", "cloudy", "light_rain"]; - return pick(choices); - }, + function fixedWeatherFor(worldRef) { + const fixed = global.TarinaiGround.fixedWeather(worldRef?.groundType || "soil") || ""; + return fixed || null; + } - resetTimer() { - return rand(CONFIG.weatherChangeMin, CONFIG.weatherChangeMax); - }, + function enforceFixedWeather(worldRef) { + const fixed = fixedWeatherFor(worldRef); + if (!fixed || !worldRef) return false; + const previous = worldRef.weather || "sunny"; + worldRef.weather = fixed; + worldRef.nextWeatherChange = resetTimer(); + if (previous !== fixed) { + worldRef.emit?.("weather:changed", { previous, next: fixed }); + worldRef.log?.(`天気: ${weatherLabel(fixed)}`); + } + return true; + } - fixedWeatherFor(worldRef) { - const fixed = global.TarinaiGround?.fixedWeather?.(worldRef?.groundType || "soil") || ""; - return fixed || null; - }, + function shouldDropRainWater(worldRef, dt = 0) { + return Boolean( + worldRef?.weather === "light_rain" && + Math.random() < Math.max(0, Number(dt) || 0) * 0.12 + ); + } - enforceFixedWeather(worldRef) { - const fixed = this.fixedWeatherFor(worldRef); - if (!fixed || !worldRef) return false; - const previous = worldRef.weather || "sunny"; - worldRef.weather = fixed; - worldRef.nextWeatherChange = this.resetTimer(); - if (previous !== fixed) { - worldRef.emit?.("weather:changed", { previous, next: fixed }); - worldRef.log?.(`\u5929\u6c17: ${weatherLabel(fixed)}`); - } - return true; - }, + function createRainWaterItem(worldRef) { + const drop = new Item("water", rand(58, worldRef.w - 58), rand(58, worldRef.h - 58)); + drop.amount = rand(24, 58); + return drop; + } - shouldDropRainWater(worldRef, dt = 0) { - return Boolean( - worldRef?.weather === "light_rain" && - Math.random() < Math.max(0, Number(dt) || 0) * 0.12 - ); - }, + function applyNextWeather(worldRef) { + if (!worldRef) return null; + if (enforceFixedWeather(worldRef)) return worldRef.weather || "sunny"; + const previous = worldRef.weather || "sunny"; + const next = chooseNext(previous); + worldRef.weather = next; + worldRef.nextWeatherChange = resetTimer(); + if (next !== previous) { + worldRef.emit?.("weather:changed", { previous, next }); + worldRef.log?.(`天気: ${weatherLabel(next)}`); + } + return next; + } - createRainWaterItem(worldRef) { - const drop = new Item("water", rand(58, worldRef.w - 58), rand(58, worldRef.h - 58)); - drop.amount = rand(24, 58); - return drop; - }, - - applyNextWeather(worldRef) { - if (!worldRef) return null; - if (this.enforceFixedWeather(worldRef)) return worldRef.weather || "sunny"; - const previous = worldRef.weather || "sunny"; - const next = this.chooseNext(previous); - worldRef.weather = next; - worldRef.nextWeatherChange = this.resetTimer(); - if (next !== previous) { - worldRef.emit?.("weather:changed", { previous, next }); - worldRef.log?.(`\u5929\u6c17: ${weatherLabel(next)}`); - } - return next; - }, + global.TarinaiWeatherSystem = Object.freeze({ + enforceFixedWeather, + shouldDropRainWater, + createRainWaterItem, + applyNextWeather, }); - - global.TarinaiWeatherSystem = WeatherSystem; })(typeof window !== "undefined" ? window : globalThis); diff --git a/js/world.js b/js/world.js index 6fd708c..0b57c63 100644 --- a/js/world.js +++ b/js/world.js @@ -28,7 +28,6 @@ class World { constructor() { this.selected = null; this.deadCount = 0; this.liveIdNext = 1; - this.liveIdFree = []; this.liveIdSerial = 0; this.liveTarinai = new Map(); this.lastBirthAt = -999; @@ -36,16 +35,12 @@ class World { constructor() { this.family = {}; this.familyVersion = 0; this.familyTreeDirty = true; - this.familyTreeDirtyReason = "init"; this.relationNotices = {}; this.resolvedFightIds = {}; this.pointer = { x: 0, y: 0, inside: false, motion: 0, movedAt: 0 }; this.hoverGrabTarget = null; this.hoverDeleteTarget = null; - this.hoverShootTarget = null; this.shootCursorActive = false; - this.shootCursorX = 0; - this.shootCursorY = 0; this.shootPointerRawX = 0; this.shootPointerRawY = 0; this.shootCursorOffsetX = 0; @@ -53,7 +48,7 @@ class World { constructor() { this.lastShootAt = -999; this.lastPhase = ""; this.weather = "sunny"; - this.worldSeed = window.TarinaiSeedFactory?.createWorldSeed?.() || `w${Date.now().toString(36)}`; + this.worldSeed = window.TarinaiSeedFactory.createWorldSeed() || `w${Date.now().toString(36)}`; this.birthSerial = 0; this.fieldType = "garden"; this.groundType = "soil"; @@ -82,9 +77,6 @@ class World { constructor() { this.itemCounts = {}; this.effectCounts = {}; this.colonyMood = { id: "relaxed", label: "\u5b89\u5b9a", description: "\u6a19\u6e96\u72b6\u614b", effects: { personality: {} } }; - this.lastColonyMoodDay = 0; - this.foodSpoilagePenalty = 0; - this.foodSpoilageEvents = 0; this.updateItemCounts(); this.updateEffectCounts(); this.markTerrainDirty?.("reset"); diff --git a/js/world_combat_effects.js b/js/world_combat_effects.js index e70463b..ec71eb2 100644 --- a/js/world_combat_effects.js +++ b/js/world_combat_effects.js @@ -44,7 +44,7 @@ blastLoosePinsFrom(x, y, blastRadius, source = null, scale = 1, salt = "blast") { let blastedPins = 0; const pinSystem = globalThis.TarinaiItemDynamicPinSystem; - const pins = (this.items || []).filter(it => it && !it.dead && typeof isPinType === "function" && isPinType(it.type)); + const pins = (this.items || []).filter(it => it && !it.dead && isPinType(it.type)); for (const pin of pins) { if (pin.pinState === "lodged") continue; let dx = pin.x - x; @@ -137,7 +137,7 @@ t.blastSpinTimer = Math.max(t.blastSpinTimer || 0, 1.35 + p * 0.70); t.blastSpinMax = Math.max(t.blastSpinMax || 0, t.blastSpinTimer); this.spawnFallEffect(t.x, t.y + t.radius * 0.45, 1.1 + p); - const explosionChance = window.TarinaiDiseaseRegistry?.infectionChance?.("disease_explosion", 0.15) ?? 0.15; + const explosionChance = window.TarinaiDiseaseRegistry.infectionChance("disease_explosion", 0.15) ?? 0.15; if (!t.dead && p > 0.22 && deterministicChance(this, "firecracker-explosion-disease", t.diseaseChance ? t.diseaseChance(explosionChance) : explosionChance, it, t)) t.infectExplosionDisease?.(it); if (!t.dead && deterministicChance(this, "firecracker-panic-bubble", 0.45, it, t)) this.spawnBubble(t.x, t.y - t.radius * 1.35, "\u306f\u3041\u3041\u3041\uff01", "rgba(84,66,86,0.80)"); } @@ -229,7 +229,7 @@ o.blastSpinTimer = Math.max(o.blastSpinTimer || 0, 1.35 + q * 0.70); o.blastSpinMax = Math.max(o.blastSpinMax || 0, o.blastSpinTimer); this.spawnFallEffect(o.x, o.y + o.radius * 0.45, 1.1 + q); - const explosionChance = window.TarinaiDiseaseRegistry?.infectionChance?.("disease_explosion", 0.15) ?? 0.15; + const explosionChance = window.TarinaiDiseaseRegistry.infectionChance("disease_explosion", 0.15) ?? 0.15; if (!o.dead && q > 0.22 && deterministicChance(this, "disease-explosion-spread", o.diseaseChance ? o.diseaseChance(explosionChance) : explosionChance, t, o)) o.infectExplosionDisease?.(t); if (!o.dead && deterministicChance(this, "disease-explosion-panic-bubble", 0.45, t, o)) this.spawnBubble(o.x, o.y - o.radius * 1.35, "\u306f\u3041\u3041\u3041\uff01", "rgba(84,66,86,0.80)"); } @@ -292,7 +292,7 @@ } else if (it.type === "grass_bed") { it.amount = 0; it.hp = 0; - global.TarinaiStructureLifecycle?.deleteItem?.(this, it, { reason: "water-hose-grass-bed", userReason: "\u6d17\u6d44\u3067\u6d41\u3055\u308c\u305f", wake: true }); + global.TarinaiStructureLifecycle.deleteItem(this, it, { reason: "water-hose-grass-bed", userReason: "\u6d17\u6d44\u3067\u6d41\u3055\u308c\u305f", wake: true }); cleaned += p * 0.95; } } @@ -339,7 +339,6 @@ it.vx = Math.cos(a) * deterministicRange(this, "zunchi-burst-speed-x", 260, 560, source, pin, i) + deterministicRange(this, "zunchi-burst-jitter-x", -40, 40, source, pin, i); it.vy = Math.sin(a) * deterministicRange(this, "zunchi-burst-speed-y", 200, 480, source, pin, i) - deterministicRange(this, "zunchi-burst-up", 30, 160, source, pin, i); it.stage = "fresh"; - it.burstFromOshibyo = true; this.addItem?.(it, "zunchi-burst") || this.items.push(it); } this.effects?.push(new Effect("zunchi_miasma", sx, sy, { size: 28, life: 0.65, color: "rgba(74,91,50,0.48)" })); @@ -352,7 +351,6 @@ const it = new Item("zunchi", clamp(x, 40, this.w - 40), clamp(y, 40, this.h - 40)); it.amount = 260; it.stage = "fresh"; - it.producedById = source?.id || ""; it.producedAt = this.time || 0; it.spawnGrace = Math.max(it.spawnGrace || 0, 10.0); it.eatProtectedUntil = (this.time || 0) + 90; @@ -421,19 +419,19 @@ }, isSleepFurniture(it) { - return Boolean(globalThis.TarinaiNestSleepSystem?.isSleepFurniture?.(it)); + return Boolean(globalThis.TarinaiNestSleepSystem.isSleepFurniture(it)); }, isTarinaiHiddenInNestBox(t) { - return Boolean(globalThis.TarinaiNestSleepSystem?.isTarinaiHiddenInNestBox?.(this, t)); + return Boolean(globalThis.TarinaiNestSleepSystem.isTarinaiHiddenInNestBox(this, t)); }, bedOccupancy(bed) { - return globalThis.TarinaiNestSleepSystem?.bedOccupancy?.(this, bed) || 0; + return globalThis.TarinaiNestSleepSystem.bedOccupancy(this, bed) || 0; }, bedComfort(bed) { - return globalThis.TarinaiNestSleepSystem?.bedComfort?.(this, bed) ?? 0.7; + return globalThis.TarinaiNestSleepSystem.bedComfort(this, bed) ?? 0.7; }, relationNotice(aId, bId, kind, cooldown = 30) { const key = [aId, bId].sort().join(":") + `:${kind}`; @@ -559,7 +557,7 @@ this.markFightPairCooldown?.(winner, loser, CONFIG.fightCooldown + 8); for (const t of [winner, loser]) { t.fightCooldown = Math.max(t.fightCooldown || 0, CONFIG.fightCooldown + deterministicRange(this, "fight-outcome-cooldown", 4, 8, t, winner, loser)); - if (typeof clearForcedBehaviorQueue === "function") clearForcedBehaviorQueue(t, e => e && e.id === "fight_rival"); + clearForcedBehaviorQueue(t, e => e && e.id === "fight_rival"); } winner.adjustPersonality?.("aggression", 0.018, "after winning fights."); loser.adjustPersonality?.("aggression", -0.018, "after losing fights."); @@ -604,7 +602,7 @@ }, itemDropImpact(it) { - return global.TarinaiImpactResponseSystem?.itemDropImpact?.(this, it); + return global.TarinaiImpactResponseSystem.itemDropImpact(this, it); }, limitBallChasers() { if (!this.itemCounts?.ball) return; @@ -661,15 +659,15 @@ }, resolveBallBallCollisions(dt) { - return global.TarinaiCollisionResponseSystem?.resolveBallBallCollisions?.(this, dt); + return global.TarinaiCollisionResponseSystem.resolveBallBallCollisions(this, dt); }, resolveBallInteractions(dt) { - return global.TarinaiCollisionResponseSystem?.resolveBallInteractions?.(this, dt); + return global.TarinaiCollisionResponseSystem.resolveBallInteractions(this, dt); }, resolveTarinaiHighSpeedCollisions(dt) { - return global.TarinaiCollisionResponseSystem?.resolveTarinaiHighSpeedCollisions?.(this, dt); + return global.TarinaiCollisionResponseSystem.resolveTarinaiHighSpeedCollisions(this, dt); } })); })(typeof window !== "undefined" ? window : globalThis); diff --git a/js/world_environment.js b/js/world_environment.js index 7b57f7c..28b01c5 100644 --- a/js/world_environment.js +++ b/js/world_environment.js @@ -4,19 +4,8 @@ const World = global.World; if (!World) throw new Error("World is not available for mixin: world_environment.js"); - function normalizedAngleLocal(angle = 0, fallback = 0) { - if (typeof normalizedItemAngle === "function") return normalizedItemAngle(angle, fallback); - const twoPi = Math.PI * 2; - const n = Number(angle); - const base = Number.isFinite(n) ? n : fallback; - return ((base % twoPi) + twoPi) % twoPi; - } - function angleForItemLocal(it) { - if (typeof itemAngleFor === "function") return itemAngleFor(it); - const type = String(it?.type || ""); - const fallback = (type === "fence_v" || type === "bounce_fence_v") ? Math.PI / 2 : 0; - return normalizedAngleLocal(it?.angle, fallback); + return itemAngleFor(it); } @@ -34,7 +23,7 @@ let reach = Math.max(32, Number(it.r || it.radius || 32) || 32); if (type === "fence" || type === "fence_v" || type === "bounce_fence" || type === "bounce_fence_v" || type === "gate_fence") reach = Math.max(120, reach * 3.9); else if (type === "nest_box") reach = Math.max(92, reach * 1.9); - else reach = global.TarinaiMechanicalShapeSystem?.reach?.(it) || Math.max(60, reach * 1.6); + else reach = global.TarinaiMechanicalShapeSystem.reach(it) || Math.max(60, reach * 1.6); return Math.max(0, distXY(x, y, Number(it.x) || 0, Number(it.y) || 0) - reach); } @@ -88,7 +77,7 @@ function sanitizeRotatorSegmentsLocal(item) { - const raw = global.TarinaiPhysicsBodySystem?.segments?.(item) || []; + const raw = global.TarinaiPhysicsBodySystem.segments(item) || []; const out = []; const clampCoord = (v) => Math.max(-420, Math.min(420, Number(v) || 0)); for (const seg of raw) { @@ -110,92 +99,17 @@ for (const seg of sanitizeRotatorSegmentsLocal(item)) { maxD = Math.max(maxD, Math.hypot(seg[0], seg[1]), Math.hypot(seg[2], seg[3])); } - return Math.min(460, maxD + Math.max(8, global.TarinaiPhysicsBodySystem?.scalar?.(item, "thickness", 12) || 12) + 8); + return Math.min(460, maxD + Math.max(8, global.TarinaiPhysicsBodySystem.scalar(item, "thickness", 12) || 12) + 8); } - function rotatorWorldSegmentsLocal(item) { - const angle = angleForItemLocal(item); - const c = Math.cos(angle); - const ss = Math.sin(angle); - const cx = Number(item?.x) || 0; - const cy = Number(item?.y) || 0; - return sanitizeRotatorSegmentsLocal(item).map(([x1, y1, x2, y2]) => [ - cx + x1 * c - y1 * ss, - cy + x1 * ss + y1 * c, - cx + x2 * c - y2 * ss, - cy + x2 * ss + y2 * c, - ]); - } - function rotatorSegmentRectLocal(item, x1, y1, x2, y2) { - const len = Math.max(4, Math.hypot(x2 - x1, y2 - y1)); - const angle = Math.atan2(y2 - y1, x2 - x1); - const halfW = len / 2; - const halfH = Math.max(4, Math.min(34, global.TarinaiPhysicsBodySystem?.scalar?.(item, "thickness", 12) || 12)) / 2; - const cx = (x1 + x2) / 2; - const cy = (y1 + y2) / 2; - const aabb = orientedRectAabb(cx, cy, halfW, halfH, angle); - return { - left: aabb.left, right: aabb.right, top: aabb.top, bottom: aabb.bottom, - cx, cy, halfW, halfH, angle, cos: aabb.cos, sin: aabb.sin, oriented: true, - type: "rotator", item, rotator: true, restitution: 0.72, - angularVelocity: (global.TarinaiPhysicsBodySystem?.scalar?.(item, "motorOn", true) === false ? 0 : (Number(global.TarinaiPhysicsBodySystem?.scalar?.(item, "motorSpeed", 0) || 0) || 0)) + (Number(global.TarinaiPhysicsBodySystem?.scalar?.(item, "spin", 0) || 0) || 0), - centerX: Number(item?.x) || 0, centerY: Number(item?.y) || 0, - }; - } - function rotatorObstacleRectsLocal(item) { - if (!item || item.dead || item.type !== "rotator") return []; - const rects = []; - for (const seg of rotatorWorldSegmentsLocal(item)) rects.push(rotatorSegmentRectLocal(item, seg[0], seg[1], seg[2], seg[3])); - return rects; - } - function reciprocatorAxisLocal(item) { - const fallback = angleForItemLocal(item); - const angle = typeof normalizedItemAngle === "function" ? normalizedItemAngle(global.TarinaiPhysicsBodySystem?.scalar?.(item, "railAxis", fallback), fallback) : (global.TarinaiPhysicsBodySystem?.scalar?.(item, "railAxis", fallback) || fallback); - return { x: Math.cos(angle), y: Math.sin(angle), angle }; - } - function reciprocatorSegmentRectLocal(item, x1, y1, x2, y2) { - const len = Math.max(4, Math.hypot(x2 - x1, y2 - y1)); - const segAngle = Math.atan2(y2 - y1, x2 - x1); - const halfW = len / 2; - const halfH = Math.max(4, Math.min(34, global.TarinaiPhysicsBodySystem?.scalar?.(item, "thickness", 12) || 12)) / 2; - const cx = (x1 + x2) / 2; - const cy = (y1 + y2) / 2; - const aabb = orientedRectAabb(cx, cy, halfW, halfH, segAngle); - const axis = reciprocatorAxisLocal(item); - const itemX = Number(item?.x || 0) || 0; - const itemY = Number(item?.y || 0) || 0; - const railDirection = Math.sign(Number(global.TarinaiPhysicsBodySystem?.scalar?.(item, "railDir", 1) || 1) || 1) || 1; - const motorVelocity = global.TarinaiPhysicsBodySystem?.scalar?.(item, "railOn", true) === false ? 0 : railDirection * Math.max(0, Number(global.TarinaiPhysicsBodySystem?.scalar?.(item, "railMotorSpeed", 0) || 0) || 0); - const totalVelocity = motorVelocity + (Number(global.TarinaiPhysicsBodySystem?.scalar?.(item, "slideSpeed", 0) || 0) || 0); - const vx = axis.x * totalVelocity; - const vy = axis.y * totalVelocity; - return { - left: aabb.left, right: aabb.right, top: aabb.top, bottom: aabb.bottom, - cx, cy, halfW, halfH, angle: segAngle, cos: aabb.cos, sin: aabb.sin, oriented: true, - type: "reciprocator", item, reciprocator: true, restitution: 0.70, - motionVelocityX: vx, motionVelocityY: vy, axisX: axis.x, axisY: axis.y, - }; - } - function reciprocatorObstacleRectsLocal(item) { - if (!item || item.dead || item.type !== "reciprocator") return []; - const rects = []; - for (const seg of rotatorWorldSegmentsLocal(item)) rects.push(reciprocatorSegmentRectLocal(item, seg[0], seg[1], seg[2], seg[3])); - return rects; - } function rectLocalPoint(r, x, y) { - const shared = global.TarinaiCollisionFootprints?.rectLocalPoint; - if (typeof shared === "function") return shared(r, x, y); - const dx = x - (r.cx ?? 0); - const dy = y - (r.cy ?? 0); - const c = Number.isFinite(r.cos) ? r.cos : Math.cos(r.angle || 0); - const s = Number.isFinite(r.sin) ? r.sin : Math.sin(r.angle || 0); - return { x: dx * c + dy * s, y: -dx * s + dy * c }; + return global.TarinaiCollisionFootprints.rectLocalPoint(r, x, y); } function rectWorldNormal(r, nx, ny) { @@ -424,41 +338,14 @@ }, - mechanicalSegments(it) { - return global.TarinaiMechanicalSystem?.sanitizeSegments?.(it) || sanitizeRotatorSegmentsLocal(it); - }, - rotatorWorldSegments(it) { - return global.TarinaiMechanicalShapeSystem?.worldSegments?.(it) || rotatorWorldSegmentsLocal(it); - }, rotatorExtent(it) { - return global.TarinaiMechanicalShapeSystem?.extent?.(it) || rotatorExtentLocal(it); + return global.TarinaiMechanicalShapeSystem.extent(it) || rotatorExtentLocal(it); }, - rotatorObstacleRects(it) { - if (it?.type === "rotator") return global.TarinaiMechanicalShapeSystem?.obstacleRects?.(it) || rotatorObstacleRectsLocal(it); - return []; - }, - reciprocatorObstacleRects(it) { - if (it?.type === "reciprocator") return global.TarinaiMechanicalShapeSystem?.obstacleRects?.(it) || reciprocatorObstacleRectsLocal(it); - return []; - }, - rotatorBoundsAabb(it) { - const bounds = global.TarinaiMechanicalShapeSystem?.boundsAabb?.(it); - if (bounds) return bounds; - const rects = rotatorObstacleRectsLocal(it); - if (!rects.length) return null; - return { - left: Math.min(...rects.map(r => r.left)), - right: Math.max(...rects.map(r => r.right)), - top: Math.min(...rects.map(r => r.top)), - bottom: Math.max(...rects.map(r => r.bottom)), - type: "rotator", item: it, - }; - }, solidObstacleRects(it) { if (!it || it.dead) return []; @@ -467,7 +354,7 @@ const rect = this.fenceRect(it); return rect ? [{ ...rect, type: it.type, item: it }] : []; } - if (global.TarinaiMechanicalShapeSystem?.isMechanicalType?.(it.type)) return global.TarinaiMechanicalShapeSystem?.obstacleRects?.(it) || []; + if (global.TarinaiMechanicalShapeSystem.isMechanicalType(it.type)) return global.TarinaiMechanicalShapeSystem.obstacleRects(it) || []; if (it.type === "nest_box") return this.nestBoxSolidRects(it); return []; }, @@ -575,7 +462,7 @@ }, pointInRect(x, y, r, padding = 0) { - return global.TarinaiCollisionFootprints?.pointInRect?.(x, y, r, padding) ?? false; + return global.TarinaiCollisionFootprints.pointInRect(x, y, r, padding) ?? false; }, @@ -586,9 +473,9 @@ let checked = 0; const addRectsFor = (it) => { if (!it || it.dead || it.type !== "poison_block" || it._poisonHazardQueryStamp === stamp) return false; - const reach = global.TarinaiMechanicalShapeSystem?.reach?.(it) || Math.max(60, it.r || 64); + const reach = global.TarinaiMechanicalShapeSystem.reach(it) || Math.max(60, it.r || 64); if (distXY(x, y, it.x, it.y) > radius + reach + 36) return false; - const partRects = global.TarinaiMechanicalSystem?.poisonHazardRects?.(it) || []; + const partRects = global.TarinaiMechanicalSystem.poisonHazardRects(it) || []; if (!partRects.length) return false; it._poisonHazardQueryStamp = stamp; checked += 1; @@ -628,7 +515,7 @@ const now = this.time || 0; const key = rect.item.id || "poison"; t._poisonBlockHitAt = t._poisonBlockHitAt || Object.create(null); - const body = global.TarinaiPhysicsBodySystem?.ensureBody?.(rect.item, this, { syncFromLegacy: false }); + const body = global.TarinaiPhysicsBodySystem.ensureBody(rect.item, this, { syncFromLegacy: false }); const solid = body?.collision ? body.collision.solid !== false : true; const cooldown = solid ? 0.30 : 0.42; if ((t._poisonBlockHitAt[key] || -999) + cooldown > now) return false; @@ -636,9 +523,9 @@ const velocity = body?.velocity || {}; const vx = Number((velocity.x ?? rect.item.vx) || 0) || 0; const vy = Number((velocity.y ?? rect.item.vy) || 0) || 0; - const omega = Number((velocity.angular ?? global.TarinaiPhysicsBodySystem?.scalar?.(rect.item, "spin", 0)) || 0) || 0; + const omega = Number((velocity.angular ?? global.TarinaiPhysicsBodySystem.scalar(rect.item, "spin", 0)) || 0) || 0; const speed = Math.hypot(vx, vy) + Math.abs(omega) * Math.max(28, rect.item.r || 64) * 0.35; - const base = Math.max(1, Number((body?.hazard?.damage ?? global.TarinaiPhysicsBodySystem?.scalar?.(rect.item, "damage", 7)) || 7) || 7); + const base = Math.max(1, Number((body?.hazard?.damage ?? global.TarinaiPhysicsBodySystem.scalar(rect.item, "damage", 7)) || 7) || 7); const damage = clamp(base * (solid ? 1.0 : 0.72) + speed * 0.018, 2.0, 18.0); if (typeof this.applyImpactDamage === "function") this.applyImpactDamage(t, damage, "\u6bd2\u30d6\u30ed\u30c3\u30af", { source: rect.item, x: t.x, y: t.y }); else if (typeof t.damage === "function") t.damage(damage, "\u6bd2\u30d6\u30ed\u30c3\u30af"); @@ -686,9 +573,9 @@ if (!poison || poison.dead || poison.type !== "poison_block") continue; checkedPoison += 1; if (checkedPoison > maxPoison) break; - const rects = global.TarinaiMechanicalSystem?.poisonHazardRects?.(poison) || []; + const rects = global.TarinaiMechanicalSystem.poisonHazardRects(poison) || []; if (!rects.length) continue; - const reach = global.TarinaiMechanicalSystem?.reach?.(poison) || Math.max(72, poison.r || 64); + const reach = global.TarinaiMechanicalSystem.reach(poison) || Math.max(72, poison.r || 64); const source = this.nearbyItems?.(poison.x, poison.y, reach + 96, true) || this.items || []; let checkedTargets = 0; for (const target of source) { @@ -702,7 +589,7 @@ } if (!touching) continue; const wasLive = !target.dead; - const destroyed = (globalThis.TarinaiItemDestruction || lifecycle)?.deleteItem?.(this, target, { + const destroyed = (lifecycle)?.deleteItem?.(this, target, { reason: "poison-block-contact", userReason: "毒ブロックに触れて消えた", wake: true, @@ -820,7 +707,7 @@ this.effects?.push(new Effect("ring", t.x, t.y, { size: Math.max(18, rr * 1.15), life: 0.18, color: "rgba(82,153,230,0.46)" })); } } else if (r.mechanical) { - global.TarinaiMechanicalSystem?.applySurfaceVelocityToCircle?.(t, r, nx, ny, preVx, preVy, { + global.TarinaiMechanicalSystem.applySurfaceVelocityToCircle(t, r, nx, ny, preVx, preVy, { damping: r.rotator ? 0.78 : (r.poisonBlock ? 0.74 : 0.80), surfaceScale: r.rotator ? 0.52 : (r.poisonBlock ? 0.38 : 0.64), normalBoost: r.rotator ? 42 : (r.poisonBlock ? 30 : 36), @@ -968,7 +855,7 @@ const ignoreSoft = Boolean(opts?.ignoreSoftPlacement); for (const it of this.nearbyItems(x, y, 96)) { if (it === self || it.dead) continue; - if (ignoreSoft && global.TarinaiPhysicsSoftClear?.isSoftPlacementType?.(it.type)) continue; + if (ignoreSoft && global.TarinaiPhysicsSoftClear.isSoftPlacementType(it.type)) continue; for (const r of this.solidObstacleRects(it)) { if (this.pointInRect(x, y, r, 10)) return true; } diff --git a/js/world_event_effects.js b/js/world_event_effects.js index fce16a1..ebc1bc2 100644 --- a/js/world_event_effects.js +++ b/js/world_event_effects.js @@ -2,8 +2,7 @@ (function (global) { const events = global.TarinaiEvents; - if (!events || events.__worldEventEffectsBound) return; - events.__worldEventEffectsBound = true; + if (!events) return; events.on("world:phase", () => { global.audio?.phase?.(); @@ -45,8 +44,8 @@ const entry = detail.entry; if (!entry) return; playLogSoundForEntry(entry); - if (typeof renderLog === "function") renderLog(worldRef?.logs || []); - if (typeof pushLogNotification === "function") pushLogNotification(entry); + renderLog(worldRef?.logs || []); + pushLogNotification(entry); }); events.on("tool:placed", (event) => { diff --git a/js/world_family_social.js b/js/world_family_social.js index a168a3a..9c1a7ea 100644 --- a/js/world_family_social.js +++ b/js/world_family_social.js @@ -38,10 +38,9 @@ this.selected = null; this.deadCount = 0; this.liveIdNext = 1; - this.liveIdFree = []; this.liveIdSerial = 0; this.liveTarinai = new Map(); - this.worldSeed = global.TarinaiSeedFactory?.createWorldSeed?.() || `w${Date.now().toString(36)}`; + this.worldSeed = global.TarinaiSeedFactory.createWorldSeed() || `w${Date.now().toString(36)}`; this.birthSerial = 0; this.lastBirthAt = -999; this.maxGeneration = 1; @@ -50,8 +49,6 @@ this.relationNotices = {}; this.resolvedFightIds = {}; this.fightPairCooldowns = {}; - this.foodSpoilagePenalty = 0; - this.foodSpoilageEvents = 0; this.pointer = { x: this.w / 2, y: this.h / 2, inside: false, motion: 0, movedAt: 0 }; this.cameraX = Math.max(0, (this.w - (this.viewportW || this.w)) / 2); this.cameraY = Math.max(0, (this.h - (this.viewportH || this.h)) / 2); @@ -61,7 +58,7 @@ this.clampCamera(); this.lastPhase = this.phaseName(); this.weather = "sunny"; - this.groundType = window.TarinaiGround?.exists?.(this.groundType || "soil") ? (this.groundType || "soil") : "soil"; + this.groundType = window.TarinaiGround.exists(this.groundType || "soil") ? (this.groundType || "soil") : "soil"; this.nextWeatherChange = rand(CONFIG.weatherChangeMin, CONFIG.weatherChangeMax); seedPopulation = seedPopulation ?? field.population ?? CONFIG.initialPopulation; const grassCount = field.grass ?? 18; @@ -74,7 +71,7 @@ } for (let i = 0; i < grassCount; i++) { const grass = new Item("grass", rand(70, this.w - 70), rand(70, this.h - 70)); - globalThis.TarinaiGrass?.setStage?.(grass, Math.floor(rand(0, 2.999)), { force: true }); + globalThis.TarinaiGrass.setStage(grass, Math.floor(rand(0, 2.999)), { force: true }); if (this.addItem) this.addItem(grass, "reset-grass"); else this.items.push(grass); } @@ -98,7 +95,6 @@ if (!this.liveTarinai) this.liveTarinai = new Map(); // Live IDs are never recycled. Relationships and transient targets are keyed by // live ID, so reuse can make dead friends/enemies leak onto new individuals. - this.liveIdFree = []; if (!Number.isFinite(this.liveIdNext)) this.liveIdNext = 1; if (!Number.isFinite(this.liveIdSerial)) this.liveIdSerial = 0; const id = `t${this.liveIdNext++}`; @@ -223,8 +219,6 @@ markFamilyTreeDirty(reason = "family") { this.familyTreeDirty = true; - this.familyTreeDirtyReason = reason; - this.familyTreeDirtyAt = this.time || 0; }, normalizeFamily() { @@ -356,29 +350,6 @@ } }, - validateFamily() { - this.normalizeFamily(); - const family = this.family || {}; - const issues = []; - const seenRenderIds = new Set(); - for (const n of Object.values(family)) { - if (!n?.id) continue; - const childSeen = new Set(); - for (const p of n.parents || []) { - if (!family[p]) issues.push({ type: "missing-parent", id: n.id, parent: p }); - else if (!family[p].children?.includes(n.id)) issues.push({ type: "missing-reciprocal-child", id: n.id, parent: p }); - } - for (const c of n.children || []) { - if (childSeen.has(c)) issues.push({ type: "duplicate-child", id: n.id, child: c }); - childSeen.add(c); - if (!family[c]) issues.push({ type: "missing-child", id: n.id, child: c }); - else if (!family[c].parents?.includes(n.id)) issues.push({ type: "missing-reciprocal-parent", id: n.id, child: c }); - } - if (seenRenderIds.has(n.id)) issues.push({ type: "duplicate-render-node", id: n.id }); - seenRenderIds.add(n.id); - } - return issues; - }, clearLiveReferencesTo(dead) { const deadId = dead?.id || dead?.releasedLiveId || ""; @@ -426,7 +397,7 @@ const family = this.family || {}; const nodes = Object.values(family).filter(Boolean).filter(n => n.hasPaired || (n.parents || []).length || (n.children || []).length); if (!nodes.length) return; - const components = globalThis.TarinaiFamilyGraph?.connectedComponents + const components = globalThis.TarinaiFamilyGraph.connectedComponents ? globalThis.TarinaiFamilyGraph.connectedComponents(nodes) : []; const remove = components.length @@ -440,7 +411,7 @@ }, addTarinai(opts = {}) { - opts = global.TarinaiSeedFactory?.prepareAddOptions?.(this, opts) || opts; + opts = global.TarinaiSeedFactory.prepareAddOptions(this, opts) || opts; const seedParents = Array.isArray(opts.seedParents) ? opts.seedParents.filter(p => p && typeof p === "object") : []; if (!opts.genetics && seedParents.length >= 2 && typeof inheritedGenetics === "function") { opts.genetics = inheritedGenetics(seedParents[0], seedParents[1], opts.birthSeed || opts.familyKey || ""); @@ -448,7 +419,7 @@ if (opts.genetics) { if (!Number.isFinite(opts.adultScale) && typeof adultScaleFromGenetics === "function") opts.adultScale = adultScaleFromGenetics(opts.birthSeed || opts.familyKey || "", opts.genetics); if (!Number.isFinite(opts.lifeSpan)) { - const profile = global.TarinaiSeedFactory?.birthProfile?.(opts.birthSeed || opts.familyKey || "", opts) || {}; + const profile = global.TarinaiSeedFactory.birthProfile(opts.birthSeed || opts.familyKey || "", opts) || {}; if (Number.isFinite(profile.lifeSpan)) opts.lifeSpan = profile.lifeSpan; } } @@ -463,7 +434,7 @@ }, nestBoxOccupants(box, limit = 5) { - return globalThis.TarinaiNestSleepSystem?.nestBoxOccupants?.(this, box, limit) || []; + return globalThis.TarinaiNestSleepSystem.nestBoxOccupants(this, box, limit) || []; }, pointerNestBoxInfo() { @@ -552,25 +523,19 @@ actor.target = partner || null; actor.thought = ritualReason; actor.sleeping = false; - if (typeof setBehaviorText === "function") { - setBehaviorText(actor, { need: "social", subNeed: "mate", actionId: "birth_ritual", actionLabel: ritualReason, reasonText: ritualReason, target: partner || null, phase: "acting", source: "state" }); - } else if (typeof patchTarinaiBehavior === "function") { - patchTarinaiBehavior(actor, { actionId: "birth_ritual", phase: "acting", target: partner || null, source: "state", need: "social", subNeed: "mate", reason: ritualReason, label: ritualReason, text: ritualReason }); - } + setBehaviorText(actor, { need: "social", subNeed: "mate", actionId: "birth_ritual", actionLabel: ritualReason, reasonText: ritualReason, target: partner || null, phase: "acting", source: "state" }); return true; }; applyRitualAction(a, b); applyRitualAction(b, a); - if (typeof setBehaviorText === "function") { - const aBehavior = typeof currentTarinaiBehavior === "function" ? currentTarinaiBehavior(a) : a.behavior; - const bBehavior = typeof currentTarinaiBehavior === "function" ? currentTarinaiBehavior(b) : b.behavior; - const aLoveCause = (aBehavior?.source === "love_mochi" || (a.loveMochiTimer || 0) > 0.04) ? "\u3078\u3053\u9905\u306e\u52b9\u679c" : `${b.name || "\u76f8\u624b"}\u304c\u6c17\u306b\u306a\u308b`; - const bLoveCause = (bBehavior?.source === "love_mochi" || (b.loveMochiTimer || 0) > 0.04) ? "\u3078\u3053\u9905\u306e\u52b9\u679c" : `${a.name || "\u76f8\u624b"}\u304c\u6c17\u306b\u306a\u308b`; - const aText = typeof buildReasonText === "function" ? buildReasonText("social", aBehavior?.tiedNeeds || ["social"], { id: "birth_ritual", need: "social", subNeed: "mate", label: `${b.name || "\u76f8\u624b"}\u3068\u3078\u3053\u3078\u3053\u3057\u3066\u3044\u308b` }, a, this, { causeText: aLoveCause }) : `${b.name || "\u76f8\u624b"}\u3068\u3078\u3053\u3078\u3053\u3057\u3066\u3044\u308b`; - const bText = typeof buildReasonText === "function" ? buildReasonText("social", bBehavior?.tiedNeeds || ["social"], { id: "birth_ritual", need: "social", subNeed: "mate", label: `${a.name || "\u76f8\u624b"}\u3068\u3078\u3053\u3078\u3053\u3057\u3066\u3044\u308b` }, b, this, { causeText: bLoveCause }) : `${a.name || "\u76f8\u624b"}\u3068\u3078\u3053\u3078\u3053\u3057\u3066\u3044\u308b`; - setBehaviorText(a, { need: "social", subNeed: "mate", actionId: "birth_ritual", actionLabel: `${b.name || "\u76f8\u624b"}\u3068\u3078\u3053\u3078\u3053\u3057\u3066\u3044\u308b`, reasonText: aText, causeText: aLoveCause, target: b }); - setBehaviorText(b, { need: "social", subNeed: "mate", actionId: "birth_ritual", actionLabel: `${a.name || "\u76f8\u624b"}\u3068\u3078\u3053\u3078\u3053\u3057\u3066\u3044\u308b`, reasonText: bText, causeText: bLoveCause, target: a }); - } + const aBehavior = currentTarinaiBehavior(a); + const bBehavior = currentTarinaiBehavior(b); + const aLoveCause = (aBehavior?.source === "love_mochi" || (a.loveMochiTimer || 0) > 0.04) ? "\u3078\u3053\u9905\u306e\u52b9\u679c" : `${b.name || "\u76f8\u624b"}\u304c\u6c17\u306b\u306a\u308b`; + const bLoveCause = (bBehavior?.source === "love_mochi" || (b.loveMochiTimer || 0) > 0.04) ? "\u3078\u3053\u9905\u306e\u52b9\u679c" : `${a.name || "\u76f8\u624b"}\u304c\u6c17\u306b\u306a\u308b`; + const aText = buildReasonText("social", aBehavior?.tiedNeeds || ["social"], { id: "birth_ritual", need: "social", subNeed: "mate", label: `${b.name || "\u76f8\u624b"}\u3068\u3078\u3053\u3078\u3053\u3057\u3066\u3044\u308b` }, a, this, { causeText: aLoveCause }); + const bText = buildReasonText("social", bBehavior?.tiedNeeds || ["social"], { id: "birth_ritual", need: "social", subNeed: "mate", label: `${a.name || "\u76f8\u624b"}\u3068\u3078\u3053\u3078\u3053\u3057\u3066\u3044\u308b` }, b, this, { causeText: bLoveCause }); + setBehaviorText(a, { need: "social", subNeed: "mate", actionId: "birth_ritual", actionLabel: `${b.name || "\u76f8\u624b"}\u3068\u3078\u3053\u3078\u3053\u3057\u3066\u3044\u308b`, reasonText: aText, causeText: aLoveCause, target: b }); + setBehaviorText(b, { need: "social", subNeed: "mate", actionId: "birth_ritual", actionLabel: `${a.name || "\u76f8\u624b"}\u3068\u3078\u3053\u3078\u3053\u3057\u3066\u3044\u308b`, reasonText: bText, causeText: bLoveCause, target: a }); a.reproductionTimer = CONFIG.reproductionCooldown + rand(2, 10); b.reproductionTimer = CONFIG.reproductionCooldown + rand(2, 10); a.surpriseTimer = Math.max(a.surpriseTimer, 0.35); @@ -598,8 +563,8 @@ t.birthRitualRole = 0; t.postBirthPeaceTimer = Math.max(t.postBirthPeaceTimer || 0, 1.4); t.behaviorLockTimer = Math.max(t.behaviorLockTimer || 0, 0.25); - if (typeof clearTarinaiBehavior === "function") clearTarinaiBehavior(t, { reason }); - if (typeof clearForcedBehaviorQueue === "function") clearForcedBehaviorQueue(t, e => e && (e.id === "approach_mate" || e.id === "birth_ritual")); + clearTarinaiBehavior(t, { reason }); + clearForcedBehaviorQueue(t, e => e && (e.id === "approach_mate" || e.id === "birth_ritual")); if (options.panic && t === source) { if (t.enterPanic) t.enterPanic({ target: options.target || null, reason, fear: options.fear ?? 0.95, wake: true, cause: options.cause || "birth_ritual_interrupted" }); else t.setActionState?.("panic", { target: options.target || null, reason, wake: true }); @@ -883,7 +848,7 @@ actor.intimidateTimer = Math.max(actor.intimidateTimer, deterministicRange(this, "intimidation-actor-timer", 1.05, 1.65, actor, target)); actor.intimidateTargetId = target.id; actor.setActionState("intimidate", { target, reason: "\u76f8\u624b\u3092\u5a01\u5687\u3057\u3066\u3044\u308b" }); - if (typeof setBehaviorText === "function") setBehaviorText(actor, { need: "social", subNeed: "conflict", actionId: "intimidate_enemy", actionLabel: "\u5a01\u5687\u3057\u3066\u3044\u308b", reasonText: `${target.name || "\u76f8\u624b"}\u3092\u5a01\u5687\u3057\u3066\u3044\u308b`, target, phase: "perform", source: "behavior" }); + setBehaviorText(actor, { need: "social", subNeed: "conflict", actionId: "intimidate_enemy", actionLabel: "\u5a01\u5687\u3057\u3066\u3044\u308b", reasonText: `${target.name || "\u76f8\u624b"}\u3092\u5a01\u5687\u3057\u3066\u3044\u308b`, target, phase: "perform", source: "behavior" }); this.effects.push(new Effect("ring", actor.x, actor.y - actor.radius * 0.65, { life: 0.42, size: actor.radius * 0.55, @@ -939,23 +904,19 @@ const initiator = opts?.initiator === b ? b : a; const receiver = initiator === a ? b : a; const causeFor = (self, other) => { - if (((typeof currentTarinaiBehavior === "function" ? currentTarinaiBehavior(self) : self.behavior)?.source === "fight_mochi") || (self.fightMochiTimer || 0) > 0.04) return "\u3051\u3093\u304b\u9905\u306e\u52b9\u679c"; + if (((currentTarinaiBehavior(self))?.source === "fight_mochi") || (self.fightMochiTimer || 0) > 0.04) return "\u3051\u3093\u304b\u9905\u306e\u52b9\u679c"; if (self === receiver && other === initiator) return `${other.name || "\u76f8\u624b"}\u306b\u3051\u3093\u304b\u3092\u58f2\u3089\u308c\u305f`; return `${other.name || "\u76f8\u624b"}\u304c\u6c17\u306b\u5165\u3089\u306a\u3044`; }; - const textFor = (self, other) => typeof buildReasonText === "function" - ? buildReasonText("social", (typeof getTarinaiBehaviorTiedNeeds === "function" ? getTarinaiBehaviorTiedNeeds(self, "social") : self.behavior?.tiedNeeds) || ["social"], { id: "fight_rival", need: "social", subNeed: "conflict", label: self === receiver ? "\u53cd\u6483\u3057\u3066\u3044\u308b" : `${other.name || "\u76f8\u624b"}\u3068\u55a7\u5629\u3057\u3066\u3044\u308b` }, self, this, { causeText: causeFor(self, other) }) - : `${other.name || "\u76f8\u624b"}\u3068\u55a7\u5629\u3057\u3066\u3044\u308b`; + const textFor = (self, other) => buildReasonText("social", getTarinaiBehaviorTiedNeeds(self, "social") || ["social"], { id: "fight_rival", need: "social", subNeed: "conflict", label: self === receiver ? "\u53cd\u6483\u3057\u3066\u3044\u308b" : `${other.name || "\u76f8\u624b"}\u3068\u55a7\u5629\u3057\u3066\u3044\u308b` }, self, this, { causeText: causeFor(self, other) }); const reasonA = textFor(a, b); const reasonB = textFor(b, a); a.counterAttackFromId = a === receiver ? initiator.id : null; b.counterAttackFromId = b === receiver ? initiator.id : null; a.setActionState("fight", { target: b, reason: reasonA }); b.setActionState("fight", { target: a, reason: reasonB }); - if (typeof setBehaviorText === "function") { - setBehaviorText(a, { need: "social", subNeed: "conflict", actionId: "fight_rival", actionLabel: a === receiver ? "\u53cd\u6483\u3057\u3066\u3044\u308b" : "\u55a7\u5629\u3057\u3066\u3044\u308b", reasonText: reasonA, causeText: causeFor(a, b), target: b, phase: "perform", source: "behavior" }); - setBehaviorText(b, { need: "social", subNeed: "conflict", actionId: "fight_rival", actionLabel: b === receiver ? "\u53cd\u6483\u3057\u3066\u3044\u308b" : "\u55a7\u5629\u3057\u3066\u3044\u308b", reasonText: reasonB, causeText: causeFor(b, a), target: a, phase: "perform", source: "behavior" }); - } + setBehaviorText(a, { need: "social", subNeed: "conflict", actionId: "fight_rival", actionLabel: a === receiver ? "\u53cd\u6483\u3057\u3066\u3044\u308b" : "\u55a7\u5629\u3057\u3066\u3044\u308b", reasonText: reasonA, causeText: causeFor(a, b), target: b, phase: "perform", source: "behavior" }); + setBehaviorText(b, { need: "social", subNeed: "conflict", actionId: "fight_rival", actionLabel: b === receiver ? "\u53cd\u6483\u3057\u3066\u3044\u308b" : "\u55a7\u5629\u3057\u3066\u3044\u308b", reasonText: reasonB, causeText: causeFor(b, a), target: a, phase: "perform", source: "behavior" }); a.fightTimer = Math.max(a.fightTimer, deterministicRange(this, "fight-start-timer-a", 3.2, 4.8, a, b)); b.fightTimer = Math.max(b.fightTimer, deterministicRange(this, "fight-start-timer-b", 3.2, 4.8, a, b)); a.nextHeadbutt = this.time + deterministicRange(this, "fight-start-headbutt", 0.08, 0.18, a, b); diff --git a/js/world_placement_log.js b/js/world_placement_log.js index 7c76827..4864d93 100644 --- a/js/world_placement_log.js +++ b/js/world_placement_log.js @@ -184,7 +184,7 @@ `; document.body.appendChild(dialog); - global.TarinaiPhysicsShapeEditorSystem?.decorateToolbar?.(dialog, "rotator"); + global.TarinaiPhysicsShapeEditorSystem.decorateToolbar(dialog, "rotator"); return dialog; } @@ -204,7 +204,7 @@ if (!item || (item.type !== "rotator" && item.type !== "poison_block")) return false; const isPoisonBlock = item.type === "poison_block"; const dialog = ensureRotatorEditor(); - global.TarinaiPhysicsShapeEditorSystem?.decorateToolbar?.(dialog, "rotator"); + global.TarinaiPhysicsShapeEditorSystem.decorateToolbar(dialog, "rotator"); const canvas = dialog.querySelector("#rotatorCanvas"); const ctx = canvas?.getContext?.("2d"); const speed = dialog.querySelector("#mechMotorSpeed"); @@ -304,7 +304,7 @@ for (const btn of dialog.querySelectorAll("[data-rotator-mode]")) btn.classList.toggle("active", btn.dataset.rotatorMode === mode); canvas.style.cursor = mode === "erase" ? "cell" : "crosshair"; }; - const pointSegmentDistance = (p, seg) => global.TarinaiPhysicsShapeEditorSystem?.pointSegmentDistance?.(p, seg) ?? (() => { + const pointSegmentDistance = (p, seg) => global.TarinaiPhysicsShapeEditorSystem.pointSegmentDistance(p, seg) ?? (() => { if (!p || !Array.isArray(seg) || seg.length < 4) return Infinity; const x1 = Number(seg[0]) || 0, y1 = Number(seg[1]) || 0; const x2 = Number(seg[2]) || 0, y2 = Number(seg[3]) || 0; @@ -332,7 +332,7 @@ return false; }; const setTemplate = (name) => { - const shared = global.TarinaiPhysicsShapeEditorSystem?.templateSegments?.(name); + const shared = global.TarinaiPhysicsShapeEditorSystem.templateSegments(name); if (shared) segments = shared.map(seg => seg.slice(0, 4)); cleanSegments(); draw(); }; @@ -394,7 +394,7 @@ canvas.onpointerdown = canvas.onpointermove = canvas.onpointerup = null; }; if (apply) apply.onclick = () => { - global.TarinaiHistory?.capture?.(worldRef, isPoisonBlock ? "poison-block-edit" : "rotator-edit"); + global.TarinaiHistory.capture(worldRef, isPoisonBlock ? "poison-block-edit" : "rotator-edit"); cleanSegments(); physicsApi()?.setSegments?.(item, segments.map(seg => seg.slice(0, 4)), isPoisonBlock ? "poison-shape-edit" : "rotator-shape-edit"); phySet(item, "thickness", Math.max(4, Math.min(34, Number(thickNum?.value || thick?.value || (isPoisonBlock ? 14 : 12)) || (isPoisonBlock ? 14 : 12))), "editor-thickness"); @@ -406,11 +406,11 @@ phySet(item, "motorOn", poweredInput ? Boolean(poweredInput.checked) : phyScalar(item, "motorOn", true) !== false, "editor-power"); if (phyScalar(item, "motorOn", true)) phySet(item, "spin", 0, "editor-spin-reset"); } - if (!global.TarinaiPhysicsBodySystem?.invalidateItem?.(item, isPoisonBlock ? "poison-block-edited" : "rotator-edited")) global.TarinaiMechanicalSystem?.invalidateGeometry?.(item); + if (!global.TarinaiPhysicsBodySystem.invalidateItem(item, isPoisonBlock ? "poison-block-edited" : "rotator-edited")) global.TarinaiMechanicalSystem.invalidateGeometry(item); const extent = worldRef?.rotatorExtent?.(item); if (Number.isFinite(extent)) item.r = Math.max(32, Math.min(460, extent)); item.rotatorEditorOpenAt = worldRef?.time || 0; - global.TarinaiLinkRuntime?.remapLinksForEditedMechanicalItem?.(worldRef, item, isPoisonBlock ? "poison-block-link-remap" : "rotator-link-remap"); + global.TarinaiLinkRuntime.remapLinksForEditedMechanicalItem(worldRef, item, isPoisonBlock ? "poison-block-link-remap" : "rotator-link-remap"); worldRef?.markSpatialDirty?.(isPoisonBlock ? "poison-block-edited" : "rotator-edited"); worldRef.drawListDirty = true; worldRef?.log?.(isPoisonBlock ? "\u6bd2\u30d6\u30ed\u30c3\u30af\u306e\u8a2d\u8a08\u3092\u5909\u66f4\u3057\u305f\u3002" : "\u56de\u8ee2\u4f53\u306e\u8a2d\u8a08\u3092\u5909\u66f4\u3057\u305f\u3002", "observe"); @@ -465,14 +465,14 @@ `; document.body.appendChild(dialog); - global.TarinaiPhysicsShapeEditorSystem?.decorateToolbar?.(dialog, "reciprocator"); + global.TarinaiPhysicsShapeEditorSystem.decorateToolbar(dialog, "reciprocator"); return dialog; } function openReciprocatorEditor(item, worldRef) { if (!item || item.type !== "reciprocator") return false; const dialog = ensureReciprocatorEditor(); - global.TarinaiPhysicsShapeEditorSystem?.decorateToolbar?.(dialog, "reciprocator"); + global.TarinaiPhysicsShapeEditorSystem.decorateToolbar(dialog, "reciprocator"); const canvas = dialog.querySelector("#reciprocatorCanvas"); const ctx = canvas?.getContext?.("2d"); const angle = dialog.querySelector("#reciprocatorAngle"); @@ -497,7 +497,7 @@ let drawing = false; const cx = canvas.width / 2; const cy = canvas.height / 2; - const axisRad = window.TarinaiMechanicalSystem?.railAxisAngle?.(item) ?? phyScalar(item, "railAxis", (typeof itemAngleFor === "function" ? itemAngleFor(item) : (Number(item.angle) || 0))); + const axisRad = window.TarinaiMechanicalSystem.railAxisAngle(item) ?? phyScalar(item, "railAxis", (itemAngleFor(item))); const deg = Math.round((axisRad * 180 / Math.PI) % 360 + 360) % 360; const speedVal = Math.round(Number(phyScalar(item, "railMotorSpeed", 92)) || 0); const travelVal = Math.round(Number(phyScalar(item, "railTravel", 150)) || 150); @@ -583,7 +583,7 @@ for (const btn of dialog.querySelectorAll("[data-reciprocator-mode]")) btn.classList.toggle("active", btn.dataset.reciprocatorMode === mode); canvas.style.cursor = mode === "erase" ? "cell" : "crosshair"; }; - const pointSegmentDistance = (p, seg) => global.TarinaiPhysicsShapeEditorSystem?.pointSegmentDistance?.(p, seg) ?? (() => { + const pointSegmentDistance = (p, seg) => global.TarinaiPhysicsShapeEditorSystem.pointSegmentDistance(p, seg) ?? (() => { if (!p || !Array.isArray(seg) || seg.length < 4) return Infinity; const x1 = Number(seg[0]) || 0, y1 = Number(seg[1]) || 0; const x2 = Number(seg[2]) || 0, y2 = Number(seg[3]) || 0; @@ -611,7 +611,7 @@ return false; }; const setTemplate = (name) => { - const shared = global.TarinaiPhysicsShapeEditorSystem?.templateSegments?.(name); + const shared = global.TarinaiPhysicsShapeEditorSystem.templateSegments(name); if (shared) segments = shared.map(seg => seg.slice(0, 4)); cleanSegments(); draw(); }; @@ -671,10 +671,10 @@ canvas.onpointerdown = canvas.onpointermove = canvas.onpointerup = null; }; if (apply) apply.onclick = () => { - global.TarinaiHistory?.capture?.(worldRef, "reciprocator-edit"); + global.TarinaiHistory.capture(worldRef, "reciprocator-edit"); cleanSegments(); const angleDeg = Math.max(0, Math.min(355, Number(angleNum?.value || angle?.value || 0) || 0)); - const railAxis = typeof normalizedItemAngle === "function" ? normalizedItemAngle(angleDeg * Math.PI / 180, 0) : angleDeg * Math.PI / 180; + const railAxis = normalizedItemAngle(angleDeg * Math.PI / 180, 0); phySet(item, "railAxis", railAxis, "editor-rail-axis"); phySet(item, "railMotorSpeed", Math.max(0, Math.min(360, Number(speedNum?.value || speed?.value || 92) || 0)), "editor-rail-speed"); phySet(item, "railTravel", Math.max(24, Math.min(520, Number(travelNum?.value || travel?.value || 150) || 150)), "editor-rail-travel"); @@ -683,15 +683,15 @@ if (phyScalar(item, "railOn", true)) phySet(item, "slideSpeed", 0, "editor-slide-reset"); phySet(item, "thickness", Math.max(4, Math.min(34, Number(thickNum?.value || thick?.value || 12) || 12)), "editor-thickness"); physicsApi()?.setSegments?.(item, segments.map(seg => seg.slice(0, 4)), "editor-segments"); - const sa = window.TarinaiMechanicalSystem?.railAxisAngle?.(item) ?? phyScalar(item, "railAxis", typeof itemAngleFor === "function" ? itemAngleFor(item) : (Number(item.angle) || 0)); + const sa = window.TarinaiMechanicalSystem.railAxisAngle(item) ?? phyScalar(item, "railAxis", itemAngleFor(item)); const halfTravel = Math.max(12, phyScalar(item, "railTravel", 150) * 0.5); const phase = Number(phyScalar(item, "railPhase", 0) || 0) || 0; phySet(item, "railAnchorX", item.x - Math.cos(sa) * halfTravel * phase, "editor-rail-anchor"); phySet(item, "railAnchorY", item.y - Math.sin(sa) * halfTravel * phase, "editor-rail-anchor"); - if (!global.TarinaiPhysicsBodySystem?.invalidateItem?.(item, "reciprocator-edited")) global.TarinaiMechanicalSystem?.invalidateGeometry?.(item); + if (!global.TarinaiPhysicsBodySystem.invalidateItem(item, "reciprocator-edited")) global.TarinaiMechanicalSystem.invalidateGeometry(item); const extent = worldRef?.rotatorExtent?.(item); if (Number.isFinite(extent)) item.r = Math.max(32, Math.min(460, extent)); - global.TarinaiLinkRuntime?.remapLinksForEditedMechanicalItem?.(worldRef, item, "reciprocator-link-remap"); + global.TarinaiLinkRuntime.remapLinksForEditedMechanicalItem(worldRef, item, "reciprocator-link-remap"); worldRef?.markSpatialDirty?.("reciprocator-edited"); worldRef.drawListDirty = true; worldRef?.log?.("\u5f80\u5fa9\u4f53\u306e\u8a2d\u8a08\u3092\u5909\u66f4\u3057\u305f\u3002", "observe"); @@ -709,7 +709,7 @@ function copyTargetAt(worldRef, x, y) { let blockedReason = ""; - const collision = global.TarinaiPlacementCollisionSystem || global.TarinaiCollisionFootprints; + const collision = global.TarinaiCollisionFootprints; const items = worldRef?.items || []; for (let i = items.length - 1; i >= 0; i--) { const it = items[i]; @@ -726,7 +726,7 @@ if (d <= tolerance && !blockedReason) blockedReason = "link"; continue; } - if (typeof isPinType === "function" && isPinType(type) && it.pinState === "lodged") { + if (isPinType(type) && it.pinState === "lodged") { const d = distXY(x, y, it.x, it.y); if (d <= Math.max(24, (it.r || 12) * 1.8) && !blockedReason) blockedReason = "lodged_pin"; continue; @@ -734,7 +734,7 @@ const d = distXY(x, y, it.x, it.y); let result = null; if (collision?.hitTestItem) { - const isPrecise = Boolean(global.TarinaiMechanicalSystem?.isMechanicalType?.(type) || worldRef?.isFenceType?.(type) || type === "nest_box"); + const isPrecise = Boolean(global.TarinaiMechanicalSystem.isMechanicalType(type) || worldRef?.isFenceType?.(type) || type === "nest_box"); result = collision.hitTestItem(worldRef, it, x, y, { padding: isPrecise ? 10 : 4, radiusMultiplier: isPrecise ? 1.12 : 1.18, @@ -747,9 +747,6 @@ return { item: null, reason: blockedReason }; } - function copyableItemAt(worldRef, x, y) { - return copyTargetAt(worldRef, x, y).item; - } function makeCopyBufferForItem(item) { if (!item) return null; @@ -807,7 +804,7 @@ if (Number.isFinite(Number(data.fuseMax))) item.fuseMax = Number(data.fuseMax); item.fuseTimer = Math.max(0.1, Number(item.fuseMax || 5) || 5); } - if (typeof isRotatableItemType === "function" && isRotatableItemType(item.type)) item.angle = Number(data.angle || 0) || 0; + if (isRotatableItemType(item.type)) item.angle = Number(data.angle || 0) || 0; if (item.type === "signboard") item.text = String(data.text || ""); if (item.type === "gate_fence") item.gateOpen = Boolean(data.gateOpen); if (item.type === "duplicator") { @@ -847,7 +844,7 @@ phySet(item, "slideSpeed", 0, "copy"); phySet(item, "thickness", Math.max(4, Math.min(34, Number(ph.thickness || 12) || 12)), "copy"); physicsApi()?.setSegments?.(item, Array.isArray(ph.segments) ? ph.segments.map(seg => seg.slice(0, 4)) : cloneSegmentsForRotator(item), "copy"); - const sa = window.TarinaiMechanicalSystem?.railAxisAngle?.(item) ?? phyScalar(item, "railAxis", typeof itemAngleFor === "function" ? itemAngleFor(item) : (Number(item.angle) || 0)); + const sa = window.TarinaiMechanicalSystem.railAxisAngle(item) ?? phyScalar(item, "railAxis", itemAngleFor(item)); const halfTravel = Math.max(12, phyScalar(item, "railTravel", 150) * 0.5); const phase = Number(phyScalar(item, "railPhase", 0) || 0) || 0; phySet(item, "railAnchorX", item.x - Math.cos(sa) * halfTravel * phase, "copy"); @@ -855,10 +852,10 @@ const extent = physicsShapeExtent(item); item.r = Math.max(item.r || 64, Math.min(460, extent)); } - if (global.TarinaiMechanicalSystem?.isMechanicalType?.(item.type)) { - if (!global.TarinaiPhysicsBodySystem?.invalidateItem?.(item, "item-data-restore")) global.TarinaiMechanicalSystem.invalidateGeometry?.(item); + if (global.TarinaiMechanicalSystem.isMechanicalType(item.type)) { + if (!global.TarinaiPhysicsBodySystem.invalidateItem(item, "item-data-restore")) global.TarinaiMechanicalSystem.invalidateGeometry(item); } - if (typeof isServingFoodType === "function" && isServingFoodType(item.type)) { + 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)); @@ -870,7 +867,7 @@ item.vx = 0; item.vy = 0; item.spinVelocity = 0; - if (typeof isPinType === "function" && isPinType(item.type)) { + if (isPinType(item.type)) { item.pinState = "loose"; item.pinTargetId = ""; item.pinAttachAngle = 0; @@ -896,8 +893,8 @@ if (!isEditableItem(it)) continue; let d = distXY(x, y, it.x, it.y); let hit = d <= Math.max(30, (it.r || 24) * 1.35); - if (global.TarinaiMechanicalSystem?.isMechanicalType?.(it.type) || worldRef?.isFenceType?.(it.type)) { - const result = (global.TarinaiPlacementCollisionSystem || global.TarinaiCollisionFootprints)?.hitTestItem?.(worldRef, it, x, y, { padding: 14, radiusMultiplier: 1.25 }) || { hit: false, distance: d }; + if (global.TarinaiMechanicalSystem.isMechanicalType(it.type) || worldRef?.isFenceType?.(it.type)) { + const result = global.TarinaiCollisionFootprints.hitTestItem(worldRef, it, x, y, { padding: 14, radiusMultiplier: 1.25 }) || { hit: false, distance: d }; hit = Boolean(result.hit); d = Math.min(d, result.distance ?? d); } @@ -925,20 +922,20 @@ } function isPlacementTool(tool) { - const def = typeof toolDefinition === "function" ? toolDefinition(tool) : null; - return Boolean(def?.placeable && typeof toolItemType === "function" && toolItemType(tool)); + const def = toolDefinition(tool); + return Boolean(def?.placeable && toolItemType(tool)); } function findDuplicatorAt(worldRef, x, y) { let best = null; let bestD = Infinity; - const runtime = global.TarinaiDuplicatorRuntime; + const runtime = global.TarinaiItemLifecycleRuntimeSupport; for (let i = (worldRef?.items || []).length - 1; i >= 0; i -= 1) { const it = worldRef.items[i]; if (!it || it.dead || it.type !== "duplicator") continue; const r = it.r || 34; const centerD = distXY(x, y, it.x, it.y); - const load = runtime?.loadPoint?.(it) || { x: it.x + r * 0.62, y: it.y + r * 0.24 }; + const load = runtime?.duplicatorLoadPoint?.(it) || { x: it.x + r * 0.62, y: it.y + r * 0.24 }; const slotD = distXY(x, y, load.x, load.y); const hit = Math.max(44, r * 1.45); if (centerD <= hit || slotD <= Math.max(30, r * 0.86)) { @@ -952,12 +949,12 @@ function directSetDuplicatorAt(worldRef, x, y, itemType = "") { const type = String(itemType || ""); if (!type || type === "duplicator") return false; - const runtime = global.TarinaiDuplicatorRuntime; - const loadType = runtime?.loadTypeForItem?.({ type, amount: 1, foodServingsRemaining: 1, dead: false }) || ""; + const runtime = global.TarinaiItemLifecycleRuntimeSupport; + const loadType = runtime?.duplicatorLoadTypeForItem?.({ type, amount: 1, foodServingsRemaining: 1, dead: false }) || ""; if (!loadType) return false; const duplicator = findDuplicatorAt(worldRef, x, y); if (!duplicator) return false; - const ok = runtime?.setStoredType?.(duplicator, loadType, worldRef, { direct: true, force: false }); + const ok = runtime?.duplicatorSetStoredType?.(duplicator, loadType, worldRef, { direct: true, force: false }); if (!ok) return false; duplicator.duplicatorLoadSuppressedUntil = Math.max(duplicator.duplicatorLoadSuppressedUntil || 0, (worldRef?.time || 0) + 0.45); if (typeof showToast === "function") showToast(`\u8907\u88fd\u6a5f: ${duplicator.storedFoodLabel || toolLabel(loadType)}\u3092\u30bb\u30c3\u30c8`); @@ -970,7 +967,7 @@ const key = String(type || ""); return Boolean( key === "rope" || key === "rod" - || global.TarinaiMechanicalSystem?.isMechanicalType?.(key) + || global.TarinaiMechanicalSystem.isMechanicalType(key) || (typeof isFenceItemType === "function" && isFenceItemType(key)) ); } @@ -980,9 +977,6 @@ return key === "zunchi" || key === "grass_bed" || key === "grass" || key === "water"; } - function isSoftPhysicsIgnoredItemType(type = "") { - return isSoftPlacementIgnoredItemType(type); - } function softItemRadius(item) { if (!item) return 12; @@ -992,7 +986,7 @@ function placementRectsForItem(worldRef, item) { const solid = worldRef?.solidObstacleRects?.(item) || []; if (solid.length) return solid; - if (global.TarinaiMechanicalSystem?.isMechanicalType?.(item?.type)) return global.TarinaiMechanicalSystem?.placementRects?.(item) || []; + if (global.TarinaiMechanicalSystem.isMechanicalType(item?.type)) return global.TarinaiMechanicalSystem.placementRects(item) || []; return solid; } @@ -1010,14 +1004,14 @@ } } const rects = placementRectsForItem(worldRef, placedItem); - if (rects.length) return rects.some(rect => (global.TarinaiPlacementCollisionSystem || global.TarinaiCollisionFootprints)?.rectCircleOverlap?.(rect, softItem.x, softItem.y, r, 0.5)); + if (rects.length) return rects.some(rect => global.TarinaiCollisionFootprints.rectCircleOverlap(rect, softItem.x, softItem.y, r, 0.5)); const pr = Math.max(10, Number(placedItem.r || placedItem.radius || itemRadiusFor?.(placedItem.type, 12) || 12) || 12); return distXY(placedItem.x, placedItem.y, softItem.x, softItem.y) <= pr + r; } function placementSoftSearchRadius(item) { let search = Math.max(80, Number(item?.r || item?.radius || 36) + 90); - if (global.TarinaiMechanicalSystem?.isMechanicalType?.(item?.type)) search = Math.max(search, (global.TarinaiMechanicalSystem?.reach?.(item) || item.r || 80) + 96); + if (global.TarinaiMechanicalSystem.isMechanicalType(item?.type)) search = Math.max(search, (global.TarinaiMechanicalSystem.reach(item) || item.r || 80) + 96); else if (item?.type === "rope" || item?.type === "rod") search = Math.max(search, Number(physicsApi()?.linkScalar?.(item, "len", (item.r || 40) * 2 || 80)) * 0.55 + 96); return search; } @@ -1037,7 +1031,7 @@ const items = collectSoftItemsUnderPlacementItem(worldRef, item); let removed = 0; for (const soft of items) { - const ok = (global.TarinaiItemDestruction || global.TarinaiStructureLifecycle)?.deleteItem?.(worldRef, soft, { + const ok = global.TarinaiStructureLifecycle.deleteItem(worldRef, soft, { reason, userReason: "置きものが重なった", wake: true, @@ -1052,9 +1046,6 @@ return removed; } - function physicsItemOverlapsSoftItem(worldRef, physicsItem, softItem) { - return placementItemOverlapsSoftItem(worldRef, physicsItem, softItem); - } function clearSoftItemsUnderPhysicsItem(worldRef, item, reason = "physics-overlap-clear") { if (!worldRef || !item || !isPhysicsObstacleItemType(item.type)) return 0; @@ -1062,13 +1053,8 @@ } global.TarinaiPhysicsSoftClear = Object.freeze({ - isPhysicsType: isPhysicsObstacleItemType, - isSoftType: isSoftPhysicsIgnoredItemType, isSoftPlacementType: isSoftPlacementIgnoredItemType, - overlaps: physicsItemOverlapsSoftItem, - overlapsPlacement: placementItemOverlapsSoftItem, collectForPlacement: collectSoftItemsUnderPlacementItem, - clearForPlacement: clearSoftItemsUnderPlacementItem, clearForItem: clearSoftItemsUnderPhysicsItem, }); @@ -1076,27 +1062,8 @@ return clearSoftItemsUnderPlacementItem(worldRef, item, "placement-soft-overlap-clear"); } - function forceImmediateToolVisualRefresh(worldRef, reason = "tool-change") { - if (!worldRef) return; - // Render and spatial rebuild are already performed by the main frame loop. - // Tool operations only invalidate cached views; batch placement must not - // force synchronous render/spatial work per item. - worldRef.drawListDirty = true; - if (worldRef._renderStack) worldRef._renderStack.signature = ""; - if (worldRef._visibleRenderStack) { - worldRef._visibleRenderStack.backItems = []; - worldRef._visibleRenderStack.layered = []; - worldRef._visibleRenderStack.carriedPlushies = []; - worldRef._visibleRenderStack.lodgedPins = []; - } - worldRef.markItemBucketsDirty?.(reason); - worldRef.markSpatialDirty?.(reason); - } Object.defineProperties(World.prototype, Object.getOwnPropertyDescriptors({ - findDuplicatorAt(x, y) { - return findDuplicatorAt(this, x, y); - }, directSetDuplicatorAt(x, y, itemType = "") { return directSetDuplicatorAt(this, x, y, itemType); @@ -1134,7 +1101,7 @@ if (placed) { placed.world = this; if (placed.type === "grass" || placed.type === "trace" || placed.type === "splat") this.markTerrainDirtyAt?.(placed.x, placed.y, Math.max(placed.r || 24, 36), "copy-paste"); - forceImmediateToolVisualRefresh(this, "copy-paste"); + this._forceImmediateToolVisualRefresh?.("tool-change"); const label = toolLabel(item.type); this.log?.(`${label}\u3092\u8cbc\u308a\u4ed8\u3051\u305f\u3002`, "observe"); showToast(`${label}\u3092\u8cbc\u308a\u4ed8\u3051\u307e\u3057\u305f\u3002`); @@ -1157,21 +1124,21 @@ const otherIsGate = it.type === "gate_fence"; const margin = (item.type === "gate_fence" || otherIsGate) ? 1.5 : baseMargin; for (const r of this.solidObstacleRects(it)) { - if ((global.TarinaiPlacementCollisionSystem || global.TarinaiCollisionFootprints)?.rectsOverlap?.(rect, r, margin)) return true; + if (global.TarinaiCollisionFootprints.rectsOverlap(rect, r, margin)) return true; } } return false; }, importantPlacementOverlapBlocked(item) { - return (global.TarinaiPlacementCollisionSystem || global.TarinaiCollisionFootprints)?.itemPlacementOverlapBlocked?.(this, item) ?? false; + return global.TarinaiCollisionFootprints.itemPlacementOverlapBlocked(this, item) ?? false; }, placementBlocked(item) { if (!item || item.dead) return true; if (item.type === "genkotsu") return false; const rects = this.solidObstacleRects(item); - const sharedBoundsBlocked = global.TarinaiPlacementPreviewSystem?.boundsBlocked?.(this, item, { rects }); + const sharedBoundsBlocked = global.TarinaiPlacementPreviewSystem.boundsBlocked(this, item, { rects }); if (sharedBoundsBlocked === true) return true; if (sharedBoundsBlocked !== false) { const pad = CONFIG.worldPadding || 30; @@ -1203,7 +1170,7 @@ }, placementClampPointFor(item, x = item?.x || 0, y = item?.y || 0) { - const shared = global.TarinaiPlacementPreviewSystem?.clampPointFor?.(this, item, x, y); + const shared = global.TarinaiPlacementPreviewSystem.clampPointFor(this, item, x, y); if (shared) return shared; const pad = CONFIG.worldPadding || 30; const rects = this.solidObstacleRects(item); @@ -1281,12 +1248,12 @@ if (!it || it.dead || it.type === "trace" || it.type === "splat") continue; let hit = false; let d = distXY(x, y, it.x, it.y); - if (global.TarinaiMechanicalSystem?.isMechanicalType?.(it.type)) { + if (global.TarinaiMechanicalSystem.isMechanicalType(it.type)) { const result = global.TarinaiMechanicalSystem.hitTest(this, it, x, y, { padding: 16 }); hit = Boolean(result.hit); d = Math.min(d, result.distance ?? d); } else { - const result = (global.TarinaiPlacementCollisionSystem || global.TarinaiCollisionFootprints)?.hitTestItem?.(this, it, x, y, { radiusMultiplier: it.type === "ball" ? 4.0 : 2.2 }) || { hit: false, distance: d }; + const result = global.TarinaiCollisionFootprints.hitTestItem(this, it, x, y, { radiusMultiplier: it.type === "ball" ? 4.0 : 2.2 }) || { hit: false, distance: d }; hit = Boolean(result.hit); d = Math.min(d, result.distance ?? d); } @@ -1299,7 +1266,7 @@ if (foundItem && isPinType(foundItem.type) && foundItem.pinState === "lodged") return foundItem; if (foundT) { const lodged = foundT.currentLodgedPin?.() || (this.items || []).find(it => it && isPinType(it.type) && it.pinState === "lodged" && it.pinTargetId === foundT.id); - if (lodged && lodged.detachPushpin) return lodged; + if (lodged) return lodged; } if (foundT && foundItem) { const td = distXY(x, y, foundT.x, foundT.y); @@ -1347,7 +1314,7 @@ if (!isPlacementTool(tool) && !["delete", "area_delete", "copy", "poke", "pinch", "water_hose", "rope", "rod", "new"].includes(tool)) { const editable = findEditableItemAt(this, x, y); if (editable && openEditableItemEditor(editable, this)) { - global.TarinaiCommands?.setSelection?.(this, null) || (this.selected = null); + global.TarinaiCommands.setSelection(this, null) || (this.selected = null); return; } } @@ -1359,14 +1326,14 @@ if (!found) { const editable = findEditableItemAt(this, x, y); if (editable && openEditableItemEditor(editable, this)) { - global.TarinaiCommands?.setSelection?.(this, null) || (this.selected = null); + global.TarinaiCommands.setSelection(this, null) || (this.selected = null); return; } } const wasSelectionEmpty = !this.selected; if (found && wasSelectionEmpty) this._scrollSelectedDataTopOnNextSelection = true; else this._scrollSelectedDataTopOnNextSelection = false; - global.TarinaiCommands?.setSelection?.(this, found) || (this.selected = found); + global.TarinaiCommands.setSelection(this, found) || (this.selected = found); return; } @@ -1375,14 +1342,14 @@ let mechanismTargetD = Infinity; for (let i = this.items.length - 1; i >= 0; i--) { const it = this.items[i]; - if (!it || it.dead || !global.TarinaiMechanicalSystem?.isMechanicalType?.(it.type)) continue; + if (!it || it.dead || !global.TarinaiMechanicalSystem.isMechanicalType(it.type)) continue; const result = global.TarinaiMechanicalSystem.hitTest(this, it, x, y, { padding: 20 }); - const extent = global.TarinaiMechanicalSystem.reach?.(it) || (it.r || 64); + const extent = global.TarinaiMechanicalSystem.reach(it) || (it.r || 64); const d = Math.min(distXY(x, y, it.x, it.y), result.distance ?? Infinity); if ((result.hit || distXY(x, y, it.x, it.y) <= Math.max(46, extent + 22)) && d < mechanismTargetD) { mechanismTarget = it; mechanismTargetD = d; } } if (mechanismTarget) { - if (global.TarinaiMechanicalSystem?.applyPokeImpulse?.(mechanismTarget, x, y, this)) { + if (global.TarinaiMechanicalSystem.applyPokeImpulse(mechanismTarget, x, y, this)) { const label = mechanismTarget.type === "rotator" ? "\u56de\u8ee2\u4f53" : (mechanismTarget.type === "poison_block" ? "\u6bd2\u30d6\u30ed\u30c3\u30af" : "\u5f80\u5fa9\u4f53"); const color = mechanismTarget.type === "rotator" ? "rgba(184,132,230,0.40)" : (mechanismTarget.type === "poison_block" ? "rgba(92,188,68,0.40)" : "rgba(90,150,214,0.40)"); this.effects?.push(new Effect("ring", x, y, { size: 22, life: 0.18, color })); @@ -1429,15 +1396,15 @@ if (itemType) { let px = x, py = y; const rawItem = this.applyToolSize(new Item(itemType, px, py)); - if (typeof isRotatableItemType === "function" && isRotatableItemType(itemType)) rawItem.angle = this.toolAngleFor ? this.toolAngleFor(itemType) : (typeof defaultItemAngle === "function" ? defaultItemAngle(itemType) : 0); + if (isRotatableItemType(itemType)) rawItem.angle = this.toolAngleFor ? this.toolAngleFor(itemType) : (defaultItemAngle(itemType)); if (itemType === "reciprocator") { - global.TarinaiPhysicsBodySystem?.setScalar?.(rawItem, "railAxis", rawItem.angle, "raw-item-axis"); - rawItem.angle = typeof defaultItemAngle === "function" ? defaultItemAngle(itemType) : 0; + global.TarinaiPhysicsBodySystem.setScalar(rawItem, "railAxis", rawItem.angle, "raw-item-axis"); + rawItem.angle = defaultItemAngle(itemType); } // Placement uses the same footprint that the preview validates. const placed = this.placeItem(rawItem, true); if (!placed) return; - forceImmediateToolVisualRefresh(this, "tool-place"); + this._forceImmediateToolVisualRefresh?.("tool-change"); // Item placement intentionally no longer writes observation-log entries. // Placement audio is still emitted through tool:placed. } diff --git a/js/world_spatial_budget.js b/js/world_spatial_budget.js index 0a5a3a1..0a98426 100644 --- a/js/world_spatial_budget.js +++ b/js/world_spatial_budget.js @@ -66,10 +66,10 @@ for (const t of this.tarinai || []) { if (!t || t.dead) continue; liveTarinai += 1; - const behavior = typeof currentTarinaiBehavior === "function" ? currentTarinaiBehavior(t) : t.behavior; + const behavior = currentTarinaiBehavior(t); const id = behavior?.actionId || t.state || "none"; behaviorCounts[id] = (behaviorCounts[id] || 0) + 1; - if (typeof isTarinaiBehaviorValueForced === "function" ? isTarinaiBehaviorValueForced(behavior) : Boolean(behavior?.source === "forced" || behavior?.forcedId || behavior?.forcedRequest)) forcedBehaviors += 1; + if (isTarinaiBehaviorValueForced(behavior)) forcedBehaviors += 1; if ((t.behaviorLockTimer || 0) > 0.01) lockedBehaviors += 1; } return { @@ -119,11 +119,11 @@ terrain: this.terrainDirtyStatsThisFrame || null, nearby: this.nearbyQueryStatsThisFrame || null, constraintDirty: this.constraintDirtyStatsThisFrame || null, - perf: window.TarinaiPerf?.snapshot?.() || null, + perf: window.TarinaiPerf.snapshot() || null, }; }, grassLimit() { - return window.TarinaiGround?.grassLimit?.(this.groundType || "soil", this.fieldType || "garden") ?? (CONFIG.grassLimit ?? 99); + return window.TarinaiGround.grassLimit(this.groundType || "soil", this.fieldType || "garden") ?? (CONFIG.grassLimit ?? 99); }, canAddGrass(extra = 1) { const limit = this.grassLimit ? this.grassLimit() : (CONFIG.grassLimit ?? 99); @@ -238,7 +238,7 @@ // so repeated rebuild requests in the same phase are collapsed to one rebuild. if (!force && this.spatialDirty === false && reason !== "manual") return false; const profiler = window.TarinaiPerf; - const end = profiler?.begin?.("update.spatial") || null; + const end = profiler.begin("update.spatial") || null; try { const full = reason === "manual" || reason === "reset" || !this.spatialVersion || !this.spatial?.rebuildStaticItems; if (full) { @@ -274,9 +274,6 @@ markItemBucketsDirty(reason = "manual") { this.itemBucketsDirty = true; - this.itemBucketsDirtyReason = reason; - this.lastItemBucketsDirtyReason = reason; - this.itemBucketDirtyMarksTotal = (this.itemBucketDirtyMarksTotal || 0) + 1; }, ensureItemBuckets(reason = "read") { @@ -333,9 +330,7 @@ bucket.push(it); } this.itemBucketsDirty = false; - this.itemBucketsDirtyReason = ""; this.itemBucketRebuildsTotal = (this.itemBucketRebuildsTotal || 0) + 1; - this.lastItemBucketRebuildReason = reason; }, liveItemsOfType(type, predicate = null) { diff --git a/js/world_tool_actions.js b/js/world_tool_actions.js index 4472753..8e1c9bb 100644 --- a/js/world_tool_actions.js +++ b/js/world_tool_actions.js @@ -4,24 +4,20 @@ const World = global.World; if (!World) throw new Error("World is not available for mixin: world_tool_actions.js"); - function forceImmediateToolVisualRefresh(worldRef, reason = "tool-action") { - if (!worldRef) return; - // Render and spatial rebuild are already performed by the main frame loop. - // Tool operations only invalidate cached views; batch placement must not - // force synchronous render/spatial work per item. - worldRef.drawListDirty = true; - if (worldRef._renderStack) worldRef._renderStack.signature = ""; - if (worldRef._visibleRenderStack) { - worldRef._visibleRenderStack.backItems = []; - worldRef._visibleRenderStack.layered = []; - worldRef._visibleRenderStack.carriedPlushies = []; - worldRef._visibleRenderStack.lodgedPins = []; - } - worldRef.markItemBucketsDirty?.(reason); - worldRef.markSpatialDirty?.(reason); - } Object.defineProperties(World.prototype, Object.getOwnPropertyDescriptors({ + _forceImmediateToolVisualRefresh(reason = "tool-action") { + this.drawListDirty = true; + if (this._renderStack) this._renderStack.signature = ""; + if (this._visibleRenderStack) { + this._visibleRenderStack.backItems = []; + this._visibleRenderStack.layered = []; + this._visibleRenderStack.carriedPlushies = []; + this._visibleRenderStack.lodgedPins = []; + } + this.markItemBucketsDirty?.(reason); + this.markSpatialDirty?.(reason); + }, findDeleteToolTargetAt(x, y) { let best = null; let bestD = Infinity; @@ -39,7 +35,7 @@ if (d < 18 && d < bestD) { best = it; bestD = d; } continue; } - const result = global.TarinaiCollisionFootprints?.hitTestItem?.(this, it, x, y, { padding: this.screenSizeToWorld ? this.screenSizeToWorld(12) : 12, radiusMultiplier: 2.0 }) || { hit: false, distance: d }; + const result = global.TarinaiCollisionFootprints.hitTestItem(this, it, x, y, { padding: this.screenSizeToWorld ? this.screenSizeToWorld(12) : 12, radiusMultiplier: 2.0 }) || { hit: false, distance: d }; if (result.hit && result.distance < bestD) { best = it; bestD = result.distance; } } return best; @@ -50,7 +46,7 @@ if (!target) { showToast("\u6d88\u305b\u308b\u3082\u306e\u304c\u3042\u308a\u307e\u305b\u3093\u3002"); return true; } if (isPinType(target.type) && target.pinState === "lodged") { showToast("\u523a\u3055\u3063\u305f\u92f2\u306f\u3001\u3064\u307e\u3093\u3067\u629c\u3044\u3066\u304f\u3060\u3055\u3044\u3002"); return true; } const label = toolLabel(target.type); - const removed = global.TarinaiStructureLifecycle?.deleteItem?.(this, target, { + const removed = global.TarinaiStructureLifecycle.deleteItem(this, target, { reason: "delete-tool", userReason: target.type === "grass_bed" ? "\u30d9\u30c3\u30c9\u304c\u306a\u304f\u306a\u3063\u305f" : "\u7f6e\u304d\u3082\u306e\u304c\u306a\u304f\u306a\u3063\u305f", wake: true, @@ -87,7 +83,7 @@ if (d <= radius + 8) candidates.push(it); continue; } - const result = global.TarinaiCollisionFootprints?.hitTestItem?.(this, it, x, y, { padding: radius, radiusMultiplier: 0.85 }) || { hit: d <= radius + hitR * 0.85 }; + const result = global.TarinaiCollisionFootprints.hitTestItem(this, it, x, y, { padding: radius, radiusMultiplier: 0.85 }) || { hit: d <= radius + hitR * 0.85 }; if (result.hit || d <= radius + hitR * 0.85) candidates.push(it); } if (!candidates.length) { if (!options.silentEmpty) showToast("\u7bc4\u56f2\u5185\u306b\u6d88\u305b\u308b\u3082\u306e\u304c\u3042\u308a\u307e\u305b\u3093\u3002"); return true; } @@ -95,7 +91,7 @@ const kindCounts = new Map(); for (const target of candidates) { if (!target || target.dead) continue; - const removed = global.TarinaiStructureLifecycle?.deleteItem?.(this, target, { + const removed = global.TarinaiStructureLifecycle.deleteItem(this, target, { reason: "area-delete-tool", userReason: target.type === "grass_bed" ? "\u30d9\u30c3\u30c9\u304c\u306a\u304f\u306a\u3063\u305f" : "\u7f6e\u304d\u3082\u306e\u304c\u306a\u304f\u306a\u3063\u305f", wake: true, @@ -122,7 +118,7 @@ }, linkPointLocalForItem(item, x, y, useClickPoint = false) { - const angle = typeof itemAngleFor === "function" ? itemAngleFor(item) : (Number(item?.angle) || 0); + const angle = itemAngleFor(item); const dx = (Number(x || 0) || 0) - (Number(item?.x || 0) || 0); const dy = (Number(y || 0) || 0) - (Number(item?.y || 0) || 0); if (!useClickPoint) return { localX: 0, localY: 0 }; @@ -147,8 +143,8 @@ for (let i = itemCandidates.length - 1; i >= 0; i -= 1) { const it = itemCandidates[i]; if (!it || it.dead) continue; - if ((this.tool === "rope" || this.tool === "rod") && global.TarinaiPhysicsSoftClear?.isSoftPlacementType?.(it.type)) continue; - const isMechanism = Boolean(global.TarinaiMechanicalSystem?.isMechanicalType?.(it.type)); + if ((this.tool === "rope" || this.tool === "rod") && global.TarinaiPhysicsSoftClear.isSoftPlacementType(it.type)) continue; + const isMechanism = Boolean(global.TarinaiMechanicalSystem.isMechanicalType(it.type)); const isFence = this.isFenceType?.(it.type) || false; const isConstraint = it.type === "rope" || it.type === "rod"; let hit = false; @@ -171,18 +167,18 @@ continue; } if (isMechanism || isFence) { - const result = global.TarinaiCollisionFootprints?.hitTestItem?.(this, it, x, y, { padding: 18, radiusMultiplier: 1.28 }) || { hit: false, distance: d }; + const result = global.TarinaiCollisionFootprints.hitTestItem(this, it, x, y, { padding: 18, radiusMultiplier: 1.28 }) || { hit: false, distance: d }; hit = Boolean(result.hit); d = Math.min(d, result.distance ?? d); } else { - const result = global.TarinaiCollisionFootprints?.hitTestItem?.(this, it, x, y, { radiusMultiplier: 1.35 }) || { hit: false, distance: d }; + const result = global.TarinaiCollisionFootprints.hitTestItem(this, it, x, y, { radiusMultiplier: 1.35 }) || { hit: false, distance: d }; hit = Boolean(result.hit); d = Math.min(d, result.distance ?? d); } if (!hit || d >= bestD) continue; const local = this.linkPointLocalForItem?.(it, x, y, isMechanism || isFence) || { localX: 0, localY: 0 }; best = { kind: "item", id: it.id, type: it.type, label: toolLabel(it.type), x: isMechanism || isFence ? x : it.x, y: isMechanism || isFence ? y : it.y, localX: local.localX, localY: local.localY, center: !(isMechanism || isFence) }; - if (isMechanism) global.TarinaiLinkRuntime?.snapEndpointToTarget?.(best, this); + if (isMechanism) global.TarinaiLinkRuntime.snapEndpointToTarget(best, this); bestD = d; } return best; @@ -200,8 +196,8 @@ return true; } const first = this.pendingLinkEndpoint; - global.TarinaiLinkRuntime?.snapEndpointToTarget?.(first, this); - global.TarinaiLinkRuntime?.snapEndpointToTarget?.(endpoint, this); + global.TarinaiLinkRuntime.snapEndpointToTarget(first, this); + global.TarinaiLinkRuntime.snapEndpointToTarget(endpoint, this); if (first.kind === endpoint.kind && first.id === endpoint.id && distXY(first.x, first.y, endpoint.x, endpoint.y) < 8) { showToast("\u5225\u306e\u4f4d\u7f6e\u307e\u305f\u306f\u5bfe\u8c61\u3092\u9078\u3093\u3067\u304f\u3060\u3055\u3044\u3002"); return true; @@ -227,8 +223,8 @@ const placed = this.addItem?.(item, constraintType); this.pendingLinkEndpoint = null; if (!placed) { showToast(`${toolName}\u3092\u8ffd\u52a0\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002`); return true; } - global.TarinaiPhysicsSoftClear?.clearForItem?.(this, item, `${constraintType}-soft-overlap-clear`); - forceImmediateToolVisualRefresh(this, `${constraintType}-create`); + global.TarinaiPhysicsSoftClear.clearForItem(this, item, `${constraintType}-soft-overlap-clear`); + this._forceImmediateToolVisualRefresh?.("tool-action"); this.log?.(`${toolName}\u3092\u3064\u306a\u3044\u3060\u3002`, "observe"); showToast(`${toolName}\u3092\u3064\u306a\u304e\u307e\u3057\u305f\u3002`); return true; @@ -302,12 +298,12 @@ const b = runtime?.endpointWorld?.(pb?.endpoint?.(it, 1), this); d = runtime?.pointSegmentDistance?.(x, y, a?.x ?? it.x, a?.y ?? it.y, b?.x ?? it.x, b?.y ?? it.y) ?? d; hit = d < 18; - } else if (global.TarinaiMechanicalSystem?.isMechanicalType?.(it.type)) { + } else if (global.TarinaiMechanicalSystem.isMechanicalType(it.type)) { const result = global.TarinaiMechanicalSystem.hitTest(this, it, x, y, { padding: 14 }); hit = Boolean(result.hit); d = Math.min(d, result.distance ?? d); } else { - const result = global.TarinaiCollisionFootprints?.hitTestItem?.(this, it, x, y, { radiusMultiplier: it.type === "ball" ? 3.2 : 1.65, padding: 10 }) || { hit: false, distance: d }; + const result = global.TarinaiCollisionFootprints.hitTestItem(this, it, x, y, { radiusMultiplier: it.type === "ball" ? 3.2 : 1.65, padding: 10 }) || { hit: false, distance: d }; hit = Boolean(result.hit); d = Math.min(d, result.distance ?? d); } @@ -398,7 +394,7 @@ } else if (hit?.kind === "item") { handled = true; const target = hit.target; - if (target && typeof isPinType === "function" && isPinType(target.type)) { + if (target && isPinType(target.type)) { const blasted = this.blastLoosePinsFrom?.(aim.x, aim.y, 92, target, 1.25, "shoot-tool") || 0; if (!blasted && target.pinState !== "lodged") { let dx = (target.x || 0) - aim.x; @@ -424,7 +420,7 @@ } else { const shouldRemove = Math.random() < 0.30; if (shouldRemove) { - const removed = global.TarinaiStructureLifecycle?.deleteItem?.(this, target, { + const removed = global.TarinaiStructureLifecycle.deleteItem(this, target, { reason: "shoot-tool", userReason: "\u5c04\u6483\u3067\u6d88\u3048\u305f", wake: true, diff --git a/js/world_update.js b/js/world_update.js index dcca199..114a6e1 100644 --- a/js/world_update.js +++ b/js/world_update.js @@ -5,57 +5,23 @@ if (!World) throw new Error("World is not available for mixin: world_update.js"); Object.defineProperties(World.prototype, Object.getOwnPropertyDescriptors({ - environmentScores(force = false) { - const nowScore = this.time || 0; - if (!force && this.environmentScoreCache && nowScore < (this.nextEnvironmentScoreAt || 0)) return this.environmentScoreCache; - const alive = this.tarinai || []; - const avgStress = alive.length ? alive.reduce((sum, t) => sum + (t.stress || 0), 0) / alive.length : 0; - let fightPressure = 0; - let diseaseCount = 0; - for (const t of alive) { - if (!t || t.dead) continue; - if (t.state === "fight") fightPressure += 16; - if (t.state === "intimidate" || (t.intimidateTimer || 0) > 0.04 || (t.intimidatedTimer || 0) > 0.04) fightPressure += 7; - if ((t.hurtTimer || 0) > 0.04 || (t.lastBleedAt || -999) + 30 > (this.time || 0)) fightPressure += 5; - if (t.zunchiDisease || t.sleepDisease || t.explosionDisease || t.fightDisease) diseaseCount += 1; - } - let blood = 0; - for (const ef of this.effects || []) if (ef && !ef.dead && (ef.type === "bleed" || ef.type === "splat")) blood += 1; - const zunchi = this.itemCounts?.zunchi || 0; - const splat = this.itemCounts?.splat || 0; - const trace = this.itemCounts?.trace || 0; - const antCorpse = this.itemCounts?.ant_corpse || 0; - const security = clamp(100 - fightPressure - blood * 1.6, 0, 100); - const foodSpoilage = this.foodSpoilagePenalty || 0; - const hygiene = clamp(100 - zunchi * 2.0 - splat * 1.2 - trace * 0.30 - antCorpse * 0.65 - blood * 1.8 - diseaseCount * 8.0 - foodSpoilage, 0, 100); - const happiness = clamp(100 - Math.max(0, 100 - security) * 0.32 - Math.max(0, 100 - hygiene) * 0.24 - avgStress * 0.42, 0, 100); - this.environmentScoreCache = { security, hygiene, happiness }; - this.nextEnvironmentScoreAt = nowScore + 3.0; - return this.environmentScoreCache; - }, - - - registerFoodSpoilage(amount = 0, item = null) { - const value = Math.max(0, Number(amount) || 0); - if (!value) return; - this.foodSpoilagePenalty = clamp((this.foodSpoilagePenalty || 0) + value, 0, 35); - this.foodSpoilageEvents = (this.foodSpoilageEvents || 0) + 1; - }, - hourOfDay(timeValue = this.time || 0) { return ((((timeValue || 0) / Math.max(1, CONFIG.dayLength || 120)) * 24) % 24 + 24) % 24; }, colonyMoodDefinition(id = "relaxed") { + const aliases = { + calm: "relaxed", + prickly: "confusion", + stinky: "disease_spread", + weaklings: "weakened", + zunda_party: "happy", + ball_party: "happy", + sun_party: "happy", + ant_overrun: "fearful", + }; + const canonicalId = aliases[String(id || "")] || String(id || "relaxed"); const defs = { - calm: { id: "calm", label: "\u7a4f\u3084\u304b", description: "\u793e\u4ea4\u6027\u304c\u4e0a\u304c\u308b", effects: { personality: { sociability: 0.42 } } }, - prickly: { id: "prickly", label: "\u7dca\u5f35", description: "\u653b\u6483\u6027\u304c\u4e0a\u304c\u308b", effects: { personality: { aggression: 0.46 } } }, - stinky: { id: "stinky", label: "\u4e0d\u885b\u751f", description: "\u958b\u653e\u6027\u304c\u4e0b\u304c\u308b", effects: { personality: { openness: -0.44 } } }, - weaklings: { id: "weaklings", label: "\u5f31\u4f53\u5316", description: "\u305a\u3093\u3061\u3069\u308c\u3044\u4ee5\u5916\u306e\u30b9\u30c8\u30ec\u30b9\u304c\u4e0b\u304c\u308b", effects: { personality: {} } }, - zunda_party: { id: "zunda_party", label: "\u305a\u3093\u3060\u96c6\u4e2d", description: "\u793e\u4ea4\u6027\u3068\u958b\u653e\u6027\u304c\u4e0a\u304c\u308a\u3001\u30b9\u30c8\u30ec\u30b9\u304c\u4e0b\u304c\u308b", effects: { personality: { sociability: 0.38, openness: 0.38 } } }, - ball_party: { id: "ball_party", label: "\u30dc\u30fc\u30eb\u96c6\u4e2d", description: "\u793e\u4ea4\u6027\u3068\u958b\u653e\u6027\u304c\u4e0a\u304c\u308a\u3001\u30b9\u30c8\u30ec\u30b9\u304c\u4e0b\u304c\u308b", effects: { personality: { sociability: 0.38, openness: 0.38 } } }, - sun_party: { id: "sun_party", label: "\u65e5\u5149\u6d74\u50be\u5411", description: "\u65e5\u5149\u6d74\u306e\u983b\u5ea6\u304c\u4e0a\u304c\u308b", effects: { personality: {} } }, - ant_overrun: { id: "ant_overrun", label: "\u30a2\u30ea\u5897\u52a0", description: "\u30a2\u30ea\u3092\u898b\u308b\u3068\u30d1\u30cb\u30c3\u30af\u306b\u306a\u308a\u3084\u3059\u3044", effects: { personality: {} } }, devastation: { id: "devastation", label: "\u58ca\u6ec5\u5bf8\u524d", description: "\u30b3\u30ed\u30cb\u30fc\u306e\u5b58\u7d9a\u304c\u304b\u306a\u308a\u5371\u3046\u3044\u72b6\u614b\u3067\u3059\u3002", effects: { personality: {} } }, crisis: { id: "crisis", label: "\u5371\u6a5f", description: "\u3051\u304c\u3084\u6d88\u8017\u304c\u76ee\u7acb\u3061\u3001\u7acb\u3066\u76f4\u3057\u304c\u5fc5\u8981\u306a\u72b6\u614b\u3067\u3059\u3002", effects: { personality: {} } }, confusion: { id: "confusion", label: "\u6df7\u4e71", description: "\u9a5a\u304d\u3084\u30d1\u30cb\u30c3\u30af\u3067\u843d\u3061\u7740\u304d\u304c\u5931\u308f\u308c\u3066\u3044\u307e\u3059\u3002", effects: { personality: {} } }, @@ -68,11 +34,11 @@ happy: { id: "happy", label: "\u5e78\u798f", description: "\u30b9\u30c8\u30ec\u30b9\u304c\u5c11\u306a\u304f\u3001\u4f59\u88d5\u306e\u3042\u308b\u72b6\u614b\u3067\u3059\u3002", effects: { personality: {} } }, relaxed: { id: "relaxed", label: "\u5b89\u5b9a", description: "\u5927\u304d\u306a\u554f\u984c\u304c\u5c11\u306a\u304f\u3001\u843d\u3061\u7740\u3044\u305f\u72b6\u614b\u3067\u3059\u3002", effects: { personality: {} } }, }; - return defs[id] || defs.relaxed; + return defs[canonicalId] || defs.relaxed; }, evaluateColonyMood(force = false) { - return global.TarinaiColonySituationSystem?.evaluate?.(this, force) || this.colonyMood || this.colonyMoodDefinition?.("relaxed"); + return global.TarinaiColonySituationSystem.evaluate(this, force); }, @@ -152,7 +118,7 @@ update(dt) { const simulation = window.TarinaiSimulation; - if (!simulation?.update) throw new Error("TarinaiSimulation is not available; TarinaiWorldUpdatePhases cannot be reached"); + if (!simulation?.update) throw new Error("TarinaiSimulation is not available"); return simulation.update(this, dt); }, @@ -192,7 +158,6 @@ t.vx = 0; t.vy = 0; t.entryTimer = 0; - t.lastOutOfBoundsRepairAt = this.time || 0; }; for (const t of this.tarinai || []) { if (!t || t.dead) continue; @@ -242,21 +207,10 @@ }, updateWeather(dt) { - if (window.TarinaiWeatherSystem?.enforceFixedWeather?.(this)) return; + if (window.TarinaiWeatherSystem.enforceFixedWeather(this)) return; this.nextWeatherChange -= dt; if (this.nextWeatherChange > 0) return; - if (window.TarinaiWeatherSystem?.applyNextWeather) { - window.TarinaiWeatherSystem.applyNextWeather(this); - return; - } - const current = this.weather || "sunny"; - const next = pick(current === "light_rain" ? ["sunny", "cloudy"] : ["sunny", "cloudy", "light_rain"]); - this.weather = next; - this.nextWeatherChange = rand(CONFIG.weatherChangeMin, CONFIG.weatherChangeMax); - if (next !== current) { - this.emit?.("weather:changed", { previous: current, next }); - this.log(`\u5929\u6c17: ${weatherLabel(this.weather)}`); - } + window.TarinaiWeatherSystem.applyNextWeather(this); } })); })(typeof window !== "undefined" ? window : globalThis); diff --git a/js/world_update_phases.js b/js/world_update_phases.js deleted file mode 100644 index 527c704..0000000 --- a/js/world_update_phases.js +++ /dev/null @@ -1,58 +0,0 @@ -"use strict"; - -// Layer: simulation/phase-facade -// Phase names facade. Concrete update work lives in -// js/simulation_systems.js so system_order.js can evolve without World owning -// the phase implementation details. -(function (global) { - function systems() { - const impl = global.TarinaiSimulationSystems; - if (!impl?.phases) throw new Error("TarinaiSimulationSystems is not available"); - return impl; - } - - function phase(name) { - const fn = systems().phases?.[name]; - if (typeof fn !== "function") throw new Error(`Tarinai simulation phase is not available: ${name}`); - return fn; - } - - function normalizeDelta(worldRef, dt) { return phase("normalizeDelta")(worldRef, dt); } - function updateClock(worldRef, dt) { return phase("updateClock")(worldRef, dt); } - function updateFrameSignals(worldRef, dt) { return phase("updateFrameSignals")(worldRef, dt); } - function updateEnvironment(worldRef, dt) { return phase("updateEnvironment")(worldRef, dt); } - function prepareSpatialFrame(worldRef) { return phase("prepareSpatialFrame")(worldRef); } - function updateItemsAndAnts(worldRef, dt) { return phase("updateItemsAndAnts")(worldRef, dt); } - function updateEffects(worldRef, dt) { return phase("updateEffects")(worldRef, dt); } - function updateCreatures(worldRef, dt) { return phase("updateCreatures")(worldRef, dt); } - function runMaintenance(worldRef, dt, itemCountBefore) { return phase("runMaintenance")(worldRef, dt, itemCountBefore); } - function emitPhaseChange(worldRef) { return phase("emitPhaseChange")(worldRef); } - function spawnAmbientTarinai(worldRef, dt) { return phase("spawnAmbientTarinai")(worldRef, dt); } - function spawnAmbientGrass(worldRef, dt) { return phase("spawnAmbientGrass")(worldRef, dt); } - function spawnRainWater(worldRef, dt) { return phase("spawnRainWater")(worldRef, dt); } - function finalizeDiagnostics(worldRef) { return phase("finalizeDiagnostics")(worldRef); } - - function update(worldRef, dt) { - return systems().update(worldRef, dt); - } - - global.TarinaiWorldUpdatePhases = Object.freeze({ - update, - phases: Object.freeze({ - normalizeDelta, - updateClock, - updateFrameSignals, - updateEnvironment, - prepareSpatialFrame, - updateItemsAndAnts, - updateEffects, - updateCreatures, - runMaintenance, - emitPhaseChange, - spawnAmbientTarinai, - spawnAmbientGrass, - spawnRainWater, - finalizeDiagnostics, - }), - }); -})(typeof window !== "undefined" ? window : globalThis); diff --git a/js/world_view.js b/js/world_view.js index 81deedf..7ed29c9 100644 --- a/js/world_view.js +++ b/js/world_view.js @@ -148,40 +148,36 @@ }, groundDefinition() { - return window.TarinaiGround?.definition?.(this.groundType || "soil") || GROUND_TYPES?.[this.groundType || "soil"] || GROUND_TYPES?.soil || { id: "soil", label: "\u7802", stressMultiplier: 1, grassMultiplier: 1 }; + return window.TarinaiGround.definition(this.groundType || "soil"); }, groundLabel() { - return window.TarinaiGround?.label?.(this.groundType || "soil") || this.groundDefinition().label || "\u7802"; + return window.TarinaiGround.label(this.groundType || "soil"); }, groundStressMultiplier() { - return window.TarinaiGround?.stressMultiplier?.(this.groundType || "soil") || 1; + return window.TarinaiGround.stressMultiplier(this.groundType || "soil"); }, groundGrassMultiplier() { - return window.TarinaiGround?.grassMultiplier?.(this.groundType || "soil") ?? 1; + return window.TarinaiGround.grassMultiplier(this.groundType || "soil"); }, - groundGrassGrowthMultiplier() { - return window.TarinaiGround?.grassGrowthMultiplier?.(this.groundType || "soil") ?? this.groundGrassMultiplier?.() ?? 1; - }, setGroundType(id = "soil", opts = {}) { - const next = window.TarinaiGround?.exists?.(id) ? id : "soil"; + const next = window.TarinaiGround.exists(id) ? id : "soil"; const previous = this.groundType || "soil"; this.groundType = next; if (next !== previous || opts.force) { - window.TarinaiWeatherSystem?.enforceFixedWeather?.(this); + window.TarinaiWeatherSystem.enforceFixedWeather(this); this.markTerrainDirty?.("ground-type"); this.enforceGrassLimit?.("ground-type"); this.updateItemCounts?.("ground-type"); this.drawListDirty = true; - window.TarinaiRender?.invalidateRenderCaches?.("ground-type"); - window.TarinaiGroundUI?.update?.(this); + window.TarinaiRender.invalidateRenderCaches("ground-type"); + window.TarinaiGroundUI.update(this); if (window.uiCache) window.uiCache.selectedSnapshot = ""; - if (typeof window.renderSelected === "function") window.renderSelected(); - if (typeof renderSelected === "function") renderSelected(); + renderSelected(); if (typeof document !== "undefined") { const groundTextEl = document.getElementById("statGroundType"); if (groundTextEl) groundTextEl.textContent = this.groundLabel?.() || "\u7802"; @@ -189,7 +185,7 @@ if (!opts.silent) { const label = this.groundLabel?.() || "\u7802"; this.log?.(`\u3058\u3081\u3093: ${label}`); - if (typeof showToast === "function") showToast(`\u3058\u3081\u3093\u3092 ${label} \u306b\u3057\u307e\u3057\u305f\u3002`); + showToast(`\u3058\u3081\u3093\u3092 ${label} \u306b\u3057\u307e\u3057\u305f\u3002`); } this.emit?.("ground:changed", { previous, next, definition: this.groundDefinition?.() }); } @@ -197,7 +193,7 @@ }, cycleGroundType() { - return this.setGroundType(window.TarinaiGround?.nextId?.(this.groundType || "soil") || "soil"); + return this.setGroundType(window.TarinaiGround.nextId(this.groundType || "soil")); }, fieldWorldScale() { @@ -208,39 +204,39 @@ }, isFenceType(type = "") { - return typeof isFenceItemType === "function" ? isFenceItemType(type) : ["fence_v", "fence_h", "bounce_fence", "bounce_fence_v", "gate_fence"].includes(String(type || "")); + return isFenceItemType(type); }, toolAngleFor(type = "") { const key = String(type || ""); - const fallback = typeof defaultItemAngle === "function" ? defaultItemAngle(key) : 0; + const fallback = defaultItemAngle(key); const value = this.toolAngles?.[key]; - return typeof normalizedItemAngle === "function" ? normalizedItemAngle(value, fallback) : (Number.isFinite(Number(value)) ? Number(value) : fallback); + return normalizedItemAngle(value, fallback); }, setToolAngle(type = "", angle = 0, { silent = false } = {}) { const key = String(type || ""); - if (!key || !(typeof isRotatableItemType === "function" && isRotatableItemType(key))) return false; + if (!key || !isRotatableItemType(key)) return false; if (!this.toolAngles) this.toolAngles = {}; - const fallback = typeof defaultItemAngle === "function" ? defaultItemAngle(key) : 0; - const next = typeof normalizedItemAngle === "function" ? normalizedItemAngle(angle, fallback) : angle; + const fallback = defaultItemAngle(key); + const next = normalizedItemAngle(angle, fallback); this.toolAngles[key] = next; this.drawListDirty = true; - if (!silent && typeof showToast === "function") showToast(`\u5411\u304d: ${Math.round(next * 180 / Math.PI)}\u00b0`); + if (!silent) showToast(`\u5411\u304d: ${Math.round(next * 180 / Math.PI)}\u00b0`); return true; }, rotateToolAngle(type = "", delta = 0, options = {}) { const key = String(type || ""); - const current = this.toolAngleFor ? this.toolAngleFor(key) : (typeof defaultItemAngle === "function" ? defaultItemAngle(key) : 0); + const current = this.toolAngleFor(key); return this.setToolAngle?.(key, current + Number(delta || 0), options) || false; }, baseZoomMin() { - return CONFIG.fieldZoomMin || 0.42; + return 0.42; }, baseZoomMax() { - return CONFIG.fieldZoomMax || 2.45; + return 2.45; }, fitFieldZoom() { @@ -398,11 +394,11 @@ }, toolSizeFor(type = "") { - return typeof normalizedToolSizeFor === "function" ? normalizedToolSizeFor(this, type) : ((this.toolSizes && this.toolSizes[type]) || this.toolSize || "medium"); + return normalizedToolSizeFor(this, type); }, toolSizeScale(type = "") { - return typeof toolSizeScaleFor === "function" ? toolSizeScaleFor(this, type) : ({ small: 0.68, medium: 1.0, large: 1.48 }[this.toolSizeFor(type)] || 1); + return toolSizeScaleFor(this, type); }, applyToolSize(item) { @@ -412,8 +408,8 @@ item.toolSize = size; this.toolSize = size; item.r *= scale; - if (typeof isServingFoodType === "function" && isServingFoodType(item.type)) { - const servings = typeof foodServingsForSize === "function" ? foodServingsForSize(size) : 5; + if (isServingFoodType(item.type)) { + const servings = foodServingsForSize(size); item.foodServingScale = scale; item.foodServingsMax = servings; item.foodServingsRemaining = servings; diff --git a/scripts/ai_inventory.py b/scripts/ai_inventory.py index 98f5882..1e2d8d2 100644 --- a/scripts/ai_inventory.py +++ b/scripts/ai_inventory.py @@ -74,7 +74,6 @@ JS_EXACT_DESCRIPTIONS = { "main.js": "startup, restore, UI binding, update/render loop", "math.js": "shared numeric helpers", "mechanical_system.js": "mechanical item forces, rails, ropes, pins, fences", - "patch_helpers.js": "compatibility patch helpers", "perf_profiler.js": "performance profiler and quality adaptation", "registry_base.js": "registry normalization helper base", "render.js": "main canvas renderer", diff --git a/scripts/regression_check.py b/scripts/regression_check.py index 7e59bdd..303acb5 100644 --- a/scripts/regression_check.py +++ b/scripts/regression_check.py @@ -105,7 +105,7 @@ context.globalThis = context; context.window = context; vm.createContext(context); vm.runInContext({source!r}, context, {{ filename: 'tarinai_action_spec.js' }}); -const action = context.registerTarinaiActionSpec({{ +const action = context.registerTarinaiActionSpecs([{{ id: 'smoke_action', need: 'fulfill', label: 'smoke', @@ -114,7 +114,7 @@ const action = context.registerTarinaiActionSpec({{ start(t, world, ctx) {{ t.startedWith = ctx.target.id; return true; }}, tick() {{ return 'finished'; }}, finish(t) {{ t.finished = true; return true; }}, -}}); +}}])[0]; const subject = {{}}; if (!context.canStartTarinaiAction('smoke_action', subject, {{}})) throw new Error('canStart failed'); if (!context.startTarinaiAction(action, subject, {{}})) throw new Error('start failed'); @@ -300,12 +300,11 @@ def check_runtime_diagnostics_hooks() -> None: spatial_budget = read_text(ROOT / "js/world_spatial_budget.js") debug_tools = read_text(ROOT / "js/debug_tools.js") world_update = read_text(ROOT / "js/world_update.js") - world_update_phases = read_text(ROOT / "js/world_update_phases.js") simulation_systems = read_text(ROOT / "js/simulation_systems.js") if "runtimeDiagnostics()" not in spatial_budget: fail("World.runtimeDiagnostics hook missing") - if "lastRuntimeDiagnostics" not in (world_update + world_update_phases + simulation_systems): - fail("world update phases do not publish lastRuntimeDiagnostics") + if "lastRuntimeDiagnostics" not in (world_update + simulation_systems): + fail("world update systems do not publish lastRuntimeDiagnostics") if "spatial rebuild" not in debug_tools or "behavior forced" not in debug_tools: fail("debug overlay is missing runtime diagnostic lines") spatial_budget = read_text(ROOT / "js/world_spatial_budget.js") @@ -504,15 +503,14 @@ def check_unified_item_registry() -> None: facade_source = read_text(ROOT / "js/item_registry.js") required_pairs = [ (tool_source, "const TOOL_DEFINITIONS"), - (tool_source, "global.TarinaiToolRegistry"), (visual_source, "function itemVisualDefinition"), - (visual_source, "global.TarinaiItemVisualDefinitions"), (food_source, "class FoodRegistry"), (food_source, "global.TarinaiFoodRegistry"), (effect_source, "class EffectRegistry"), (effect_source, "global.TarinaiEffectRegistry"), (facade_source, "global.TarinaiItemRegistry"), - (facade_source, "definition: itemTypeDefinition"), + (facade_source, "food: global.TarinaiFoodRegistry"), + (facade_source, "effect: global.TarinaiEffectRegistry"), ] for source, token in required_pairs: if token not in source: @@ -542,7 +540,6 @@ def check_phase_and_input_split() -> None: "js/simulation_maintenance_system.js", "js/simulation_ambient_system.js", "js/simulation_systems.js", - "js/world_update_phases.js", "js/ui_input_shared.js", "js/ui_input_touch.js", "js/ui_input_mouse.js", @@ -551,16 +548,15 @@ def check_phase_and_input_split() -> None: if rel not in js_files: fail(f"split runtime module missing from manifest: {rel}") world_update = read_text(ROOT / "js/world_update.js") - phases = read_text(ROOT / "js/world_update_phases.js") ui_bind = read_text(ROOT / "js/ui_bind.js") touch = read_text(ROOT / "js/ui_input_touch.js") mouse = read_text(ROOT / "js/ui_input_mouse.js") shared = read_text(ROOT / "js/ui_input_shared.js") systems = read_text(ROOT / "js/simulation_systems.js") - if "TarinaiSimulation" not in world_update or "function update(worldRef, dt)" not in phases: - fail("World.update is not delegated to world_update_phases.js") + if "TarinaiSimulation" not in world_update: + fail("World.update is not delegated to TarinaiSimulation") for token in ["updateClock", "updateEnvironment", "updateItemsAndAnts", "updateCreatures", "runMaintenance", "spawnRainWater"]: - if token not in phases or token not in systems: + if token not in systems: fail(f"world update phase missing: {token}") if "touchstart" in ui_bind or "mousedown" in ui_bind or "touchmove" in ui_bind: fail("ui_bind.js still owns field pointer input handlers") @@ -573,7 +569,7 @@ def check_phase_and_input_split() -> None: fail("mouse input module missing mouse handlers") if ui_bind.count("syncToolSizeBadges();") != 1: fail("ui_bind.js should bind/sync tool size badges only once") - phase_birth_direct = "audio.birth" in phases + phase_birth_direct = False placement_birth_direct = "audio.birth" in read_text(ROOT / "js/world_placement_log.js") family_birth_direct = "audio.birth" in read_text(ROOT / "js/world_family_social.js") if phase_birth_direct or placement_birth_direct or family_birth_direct: @@ -586,7 +582,6 @@ def check_processing_split() -> None: js_files = data["js"] required = [ "js/item_type_initializers.js", - "js/item_lifecycle_runtime.js", "js/item_render_runtime.js", "js/tarinai_item_targeting.js", "js/tarinai_action_definitions.js", @@ -597,16 +592,15 @@ def check_processing_split() -> None: for rel in required: if rel not in js_files: fail(f"processing split module missing from manifest: {rel}") - if not (js_files.index("js/items.js") < js_files.index("js/item_type_initializers.js") < js_files.index("js/item_lifecycle_runtime.js") < js_files.index("js/item_render_runtime.js") < js_files.index("js/ants.js")): - fail("item init/runtime/render split load order is invalid") + if not (js_files.index("js/items.js") < js_files.index("js/item_type_initializers.js") < js_files.index("js/item_render_runtime.js") < js_files.index("js/ants.js")): + fail("item init/render split load order is invalid") if not (js_files.index("js/tarinai_forced_behavior.js") < js_files.index("js/tarinai_item_targeting.js") < js_files.index("js/tarinai_consumable_behavior.js") < js_files.index("js/tarinai_social_action_runtime.js") < js_files.index("js/tarinai_building_behavior.js") < js_files.index("js/tarinai_action_definitions.js") < js_files.index("js/tarinai_needs_items.js")): fail("tarinai behavior split load order is invalid") items = read_text(ROOT / "js/items.js") init = read_text(ROOT / "js/item_type_initializers.js") - lifecycle = read_text(ROOT / "js/item_lifecycle_runtime.js") item_render = read_text(ROOT / "js/item_render_runtime.js") - if "class Item" not in items or "function initializeItemTypeState" not in init or "Object.assign(Item.prototype" not in lifecycle or "Object.assign(Item.prototype" not in item_render: - fail("item construction/init/lifecycle/render split is incomplete") + if "class Item" not in items or "function initializeItemTypeState" not in init or "Object.assign(Item.prototype" not in item_render: + fail("item construction/init/render split is incomplete") if "updateDuplicator" in items or "\ndraw(ctx" in items or "if (type === \"grass\")" in items: fail("items.js still owns type init, runtime, or render methods") defs = read_text(ROOT / "js/tarinai_action_definitions.js") @@ -666,7 +660,8 @@ if (!context.TarinaiCommands.dispatch(world, {{ type: 'selection.favorite.toggle if (!context.TarinaiCommands.dispatch(world, {{ type: 'simulation.paused.toggle' }}).ok || world.paused !== true) throw new Error('pause failed'); if (!context.TarinaiCommands.dispatch(world, {{ type: 'simulation.speed.cycle', speeds: [1, 2, 4] }}).ok || world.speed !== 2) throw new Error('speed failed'); if (!context.TarinaiCommands.dispatch(world, {{ type: 'pointer.primary', x: 7, y: 9 }}).ok || world.clicked[0] !== 7 || world.clicked[1] !== 9) throw new Error('pointer failed'); -if (typeof world.dispatchCommand !== 'function' || !world.dispatchCommand({{ type: 'selection.clear' }}).ok || world.selected !== null) throw new Error('World facade failed'); +if (typeof world['dispatch' + 'Command'] !== 'undefined') throw new Error('World facade should not be installed'); +if (!context.TarinaiCommands.dispatch(world, {{ type: 'selection.clear' }}).ok || world.selected !== null) throw new Error('selection clear failed'); if (!events.some(([type]) => type === 'selection:changed')) throw new Error('selection event missing'); """ with tempfile.NamedTemporaryFile("w", encoding="utf-8", suffix=".js", delete=False) as f: @@ -695,7 +690,6 @@ def check_system_order_boundary() -> None: "js/simulation_maintenance_system.js", "js/simulation_ambient_system.js", "js/simulation_systems.js", - "js/world_update_phases.js", "js/system_order.js", "js/simulation.js", "js/world_update.js", @@ -712,7 +706,6 @@ def check_system_order_boundary() -> None: "js/simulation_maintenance_system.js", "js/simulation_ambient_system.js", "js/simulation_systems.js", - "js/world_update_phases.js", "js/system_order.js", "js/simulation.js", "js/world_update.js", @@ -736,13 +729,11 @@ def check_system_order_boundary() -> None: for rel, token in concrete_modules.items(): if token not in read_text(ROOT / rel): fail(f"concrete simulation split module missing export: {rel}") - if "TarinaiSimulationSystems" not in read_text(ROOT / "js/world_update_phases.js"): - fail("world_update_phases.js is not delegating to the concrete simulation systems module") for token in ["const SYSTEM_ORDER", "clock", "environment", "items_and_ants", "creatures", "maintenance", "diagnostics"]: if token not in system_order: fail(f"system order token missing: {token}") - if "TarinaiSystemOrder" not in simulation or "systemOrder" not in simulation: - fail("simulation.js does not expose the named system order boundary") + if "TarinaiSystemOrder" not in simulation or "Object.freeze({ update })" not in simulation: + fail("simulation.js does not expose the update boundary") ok("named simulation system order present") @@ -756,7 +747,6 @@ def simulation_boundary_smoke() -> None: "js/simulation_maintenance_system.js", "js/simulation_ambient_system.js", "js/simulation_systems.js", - "js/world_update_phases.js", "js/system_order.js", "js/simulation.js", ]} @@ -770,9 +760,8 @@ for (const [filename, source] of Object.entries({sources!r})) {{ vm.runInContext(source, context, {{ filename }}); }} if (!context.TarinaiSimulationSystems?.phases?.updateClock) throw new Error('simulation systems missing'); -if (!context.TarinaiWorldUpdatePhases?.phases?.updateClock) throw new Error('phase facade missing'); -if (!context.TarinaiSystemOrder?.names?.includes('clock')) throw new Error('system order missing'); -if (!context.TarinaiSimulation?.systemOrder) throw new Error('simulation facade missing'); +if (!context.TarinaiSystemOrder?.update) throw new Error('system order missing'); +if (!context.TarinaiSimulation?.update) throw new Error('simulation facade missing'); """ with tempfile.NamedTemporaryFile("w", encoding="utf-8", suffix=".js", delete=False) as f: f.write(js) @@ -796,7 +785,7 @@ def check_update_policy_boundaries() -> None: for rel in required: if rel not in js_files: fail(f"update policy boundary module missing from manifest: {rel}") - if not (js_files.index("js/item_lifecycle_runtime.js") < js_files.index("js/item_update_policy.js") < js_files.index("js/item_runtime.js")): + if not (js_files.index("js/item_lifecycle_support.js") < js_files.index("js/item_update_policy.js") < js_files.index("js/item_runtime.js")): fail("item update policy load order is invalid") if not (js_files.index("js/simulation_runtime_helpers.js") < js_files.index("js/tarinai_update_policy.js") < js_files.index("js/simulation_creature_system.js")): fail("Tarinai update policy load order is invalid") @@ -810,7 +799,7 @@ def check_update_policy_boundaries() -> None: (item_policy, "itemUpdateInterval"), (item_policy, "forScheduledItems"), (tarinai_policy, "global.TarinaiCreatureUpdatePolicy"), - (tarinai_policy, "updateInterval"), + (tarinai_policy, "updateCadence"), (scheduler, "TarinaiItemUpdatePolicy"), (sim_helpers, "TarinaiItemUpdatePolicy"), (creature_system, "TarinaiCreatureUpdatePolicy"), @@ -834,6 +823,9 @@ context.isServingFoodType = (type) => type === 'food'; context.TarinaiItemRuntime = {{ updateOne(item, dt) {{ item.runs = (item.runs || 0) + 1; item.lastDt = dt; return true; }} }}; +context.TarinaiPerf = {{ begin() {{ return () => undefined; }}, renderQualityTier() {{ return "normal"; }} }}; +context.TarinaiPhysicsWorldSystem = {{ updateWorld() {{ return {{ mechanical: 0, constraintRan: 0, postConstraintPairs: 0 }}; }} }}; +context.TarinaiInputMode = {{ snapshot() {{ return {{ touchFirst: false }}; }} }}; vm.createContext(context); for (const file of ['js/item_update_policy.js', 'js/item_update_scheduler.js']) {{ vm.runInContext(fs.readFileSync(file, 'utf8'), context, {{ filename: file }}); @@ -864,7 +856,7 @@ def check_entity_runtime_boundary() -> None: for rel in required: if rel not in js_files: fail(f"entity runtime boundary module missing from manifest: {rel}") - if not (js_files.index("js/item_lifecycle_runtime.js") < js_files.index("js/item_runtime.js") < js_files.index("js/structure_lifecycle.js")): + if not (js_files.index("js/item_lifecycle_pipeline.js") < js_files.index("js/item_runtime.js") < js_files.index("js/structure_lifecycle.js")): fail("item runtime boundary load order is invalid") if not (js_files.index("js/tarinai_needs_items.js") < js_files.index("js/tarinai_social_move_life.js") < js_files.index("js/tarinai_runtime.js") < js_files.index("js/tarinai_render.js")): fail("Tarinai runtime boundary load order is invalid") @@ -878,10 +870,9 @@ def check_entity_runtime_boundary() -> None: (item_runtime, "installPrototypeBoundary"), (item_runtime, "updateScheduled"), (tarinai_runtime, "global.TarinaiCreatureRuntime"), - (tarinai_runtime, "updateBatch"), - (item_scheduler, "TarinaiItemRuntime?.updateOne"), - (sim_helpers, "TarinaiItemRuntime?.updateScheduled"), - (creature_system, "TarinaiCreatureRuntime?.updateOne"), + (item_scheduler, "TarinaiItemRuntime.updateOne"), + (sim_helpers, "TarinaiItemRuntime.updateScheduled"), + (creature_system, "TarinaiCreatureRuntime.updateOne"), ] for source, token in required_tokens: if token not in source: @@ -896,8 +887,6 @@ def check_tarinai_update_pipeline_boundary() -> None: "js/tarinai_sunbath_system.js", "js/tarinai_cursor_care_system.js", "js/tarinai_local_environment_system.js", - "js/tarinai_forced_action_planner_system.js", - "js/tarinai_action_planner_system.js", "js/tarinai_need_planner_system.js", "js/tarinai_item_interaction_context.js", "js/tarinai_food_interaction_system.js", @@ -915,8 +904,6 @@ def check_tarinai_update_pipeline_boundary() -> None: fail(f"Tarinai update pipeline module missing from manifest: {rel}") expected_order = [ "js/tarinai_needs_items.js", - "js/tarinai_forced_action_planner_system.js", - "js/tarinai_action_planner_system.js", "js/tarinai_need_planner_system.js", "js/tarinai_item_interaction_context.js", "js/tarinai_food_interaction_system.js", @@ -940,8 +927,6 @@ def check_tarinai_update_pipeline_boundary() -> None: "js/tarinai_sunbath_system.js": "global.TarinaiSunbathSystem", "js/tarinai_cursor_care_system.js": "global.TarinaiCursorCareSystem", "js/tarinai_local_environment_system.js": "global.TarinaiLocalEnvironmentSystem", - "js/tarinai_forced_action_planner_system.js": "global.TarinaiForcedActionPlannerSystem", - "js/tarinai_action_planner_system.js": "global.TarinaiActionPlannerSystem", "js/tarinai_need_planner_system.js": "global.TarinaiNeedPlannerSystem", "js/tarinai_item_interaction_context.js": "global.TarinaiItemInteractionContext", "js/tarinai_food_interaction_system.js": "global.TarinaiFoodInteractionSystem", @@ -966,11 +951,11 @@ def check_tarinai_update_pipeline_boundary() -> None: cursor_system = read_text(ROOT / "js/tarinai_cursor_care_system.js") local_env_system = read_text(ROOT / "js/tarinai_local_environment_system.js") interaction_system = read_text(ROOT / "js/tarinai_item_interaction_system.js") - if "TarinaiUpdatePipeline?.updateOne" not in needs_items or "const minute = dt / 60" in needs_items: - fail("Tarinai.prototype.update is not a thin facade over the update pipeline") - if "TarinaiItemInteractionSystem?.updateOne" not in needs_items or "const foodNeed = Number(this.needRaw?.food" in needs_items: + if " update(dt)" in needs_items or "const minute = dt / 60" in needs_items: + fail("Tarinai.prototype.update residue remains in needs mixin") + if "TarinaiItemInteractionSystem.updateOne" not in needs_items or "const foodNeed = Number(this.needRaw?.food" in needs_items: fail("Tarinai.prototype.interactWithItems is not a thin facade over the interaction system") - for facade_token in ["TarinaiSunbathSystem?.updateOne", "TarinaiCursorCareSystem?.updateOne", "TarinaiLocalEnvironmentSystem?.updateOne"]: + for facade_token in ["TarinaiSunbathSystem.updateOne", "TarinaiCursorCareSystem.updateOne", "TarinaiLocalEnvironmentSystem.updateOne"]: if facade_token not in needs_items: fail(f"Tarinai prototype subsystem facade missing: {facade_token}") interaction_context = read_text(ROOT / "js/tarinai_item_interaction_context.js") @@ -986,11 +971,11 @@ def check_tarinai_update_pipeline_boundary() -> None: ]: if token not in source: fail(f"Tarinai update subsystem missing extracted token: {token}") - if "TarinaiItemInteractionContext?.create" not in interaction_system or "TarinaiFoodInteractionSystem?.updateCandidate" not in interaction_system or "TarinaiContactItemSystem?.updateCandidate" not in interaction_system: + if "TarinaiItemInteractionContext.create" not in interaction_system or "TarinaiFoodInteractionSystem.updateCandidate" not in interaction_system or "TarinaiContactItemSystem.updateCandidate" not in interaction_system: fail("Tarinai item interaction orchestrator does not delegate to context/food/contact candidate subsystems") if "for (const item of ctx.foodCandidates" not in interaction_system: fail("Tarinai item interaction orchestrator does not preserve per-candidate interaction order") - if "TarinaiNeedPlannerSystem?.updateOne" not in needs_items or "function resolveNeedsCore" not in needs_items: + if "TarinaiNeedPlannerSystem.updateOne" not in needs_items or "function resolveNeedsCore" not in needs_items: fail("Tarinai resolveNeeds is not routed through planner system") if "TarinaiNeedPlannerCoreSystem" not in needs_items: fail("Tarinai need planner core system export missing") @@ -1018,6 +1003,9 @@ context.clamp = (v, a, b) => Math.max(a, Math.min(b, v)); context.rand = (a, b) => (a + b) / 2; context.pick = (arr) => arr[0]; context.stableUnit = () => 0.5; +context.deterministicRange = (world, salt, min, max) => (min + max) / 2; +context.deterministicSigned = () => 0; +context.deterministicChance = () => false; context.applyNeedShock = () => undefined; context.applyNeedRelief = () => undefined; context.calculateStressFromNeeds = () => 0; @@ -1044,7 +1032,7 @@ const world = {{ }}; const t = {{ world, dead: false, x: 10, y: 12, vx: 0, vy: 0, radius: 20, - age: 0, reproductionTimer: 10, blink: 0, eatTimer: 0, eatCooldown: 0, foodReactTimer: 0, + age: 0, reproductionTimer: 10, eatTimer: 0, eatCooldown: 0, foodReactTimer: 0, surpriseTimer: 0, fearTimer: 0, intimidateTimer: 0, intimidatedTimer: 0, stress: 0, hpBarTimer: 0, stressBarTimer: 0, loveMochiTimer: 0, fightMochiTimer: 0, birthRitualTimer: 0, pokeFlashTimer: 0, cursorPetting: 0, pinchThoughtTimer: 0, @@ -1064,7 +1052,6 @@ const t = {{ updateNestBoxPresence() {{ calls.push('nestBox'); }}, checkLife() {{ calls.push('checkLife'); }}, }}; -if (!context.TarinaiUpdatePipeline.canRunConcretePipeline()) throw new Error('pipeline cannot run concrete steps'); if (!context.TarinaiUpdatePipeline.updateOne(t, 0.016, {{ legacyUpdate() {{ throw new Error('legacy should not run'); }} }})) throw new Error('pipeline update failed'); for (const expected of ['zunchiDisease', 'specialDisease', 'facing', 'targetProgress', 'move', 'nestBox', 'fence', 'checkLife']) {{ if (!calls.includes(expected)) throw new Error('missing pipeline call: ' + expected); @@ -1098,6 +1085,8 @@ def check_item_lifecycle_pipeline_boundary() -> None: ] required = [ "js/item_lifecycle_support.js", + "js/physics_helpers.js", + "js/pin_attachment_system.js", *dynamic_required, "js/item_lifecycle_decay_system.js", "js/item_lifecycle_growth_system.js", @@ -1111,8 +1100,9 @@ def check_item_lifecycle_pipeline_boundary() -> None: if rel not in js_files: fail(f"Item lifecycle pipeline module missing from manifest: {rel}") expected_order = [ + "js/physics_helpers.js", + "js/pin_attachment_system.js", "js/item_lifecycle_support.js", - "js/item_lifecycle_runtime.js", "js/item_update_policy.js", *dynamic_required, "js/item_lifecycle_decay_system.js", @@ -1154,7 +1144,7 @@ def check_item_lifecycle_pipeline_boundary() -> None: fail(f"Item lifecycle pipeline missing step token: {token}") dynamic_step = read_text(ROOT / "js/item_lifecycle_step_dynamic.js") dynamic_system = read_text(ROOT / "js/item_dynamic_system.js") - if "TarinaiItemDynamicSystem?.update" not in dynamic_step: + if "TarinaiItemDynamicSystem.update" not in dynamic_step: fail("Item dynamic lifecycle step does not delegate to TarinaiItemDynamicSystem") for token in ["TarinaiItemDynamicToolSystem", "TarinaiItemDynamicBallSystem", "TarinaiItemDynamicDuplicatorSystem", "TarinaiItemDynamicPinSystem", "TarinaiItemDynamicZunchiSystem"]: if token not in dynamic_system: @@ -1164,20 +1154,15 @@ def check_item_lifecycle_pipeline_boundary() -> None: for token in ["item.updateFan", "item.updateRotator", "item.updateBall", "item.updateDuplicator", "item.updatePushpin", "item.updateZunchiMotion", "item.updateZunchi"]: if token in body: fail(f"dynamic subsystem still delegates to prototype method {token} in {rel}") - lifecycle_runtime = read_text(ROOT / "js/item_lifecycle_runtime.js") lifecycle_support = read_text(ROOT / "js/item_lifecycle_support.js") lifecycle_decay = read_text(ROOT / "js/item_lifecycle_step_decay.js") lifecycle_growth = read_text(ROOT / "js/item_lifecycle_step_growth.js") - if "TarinaiItemLifecycleRuntimeSupport" not in lifecycle_support or "TarinaiDuplicatorRuntime" not in lifecycle_support: + if "TarinaiItemLifecycleRuntimeSupport" not in lifecycle_support: fail("item lifecycle support export missing") - if "TarinaiItemDecaySystem?.update" not in lifecycle_decay or "TarinaiItemGrowthSystem?.update" not in lifecycle_growth: + if "TarinaiDuplicatorRuntime" in lifecycle_support: + fail("duplicate duplicator runtime export still present") + if "TarinaiItemDecaySystem.update" not in lifecycle_decay or "TarinaiItemGrowthSystem.update" not in lifecycle_growth: fail("item lifecycle decay/growth steps are not thin system adapters") - for helper_token in ["function duplicatorLoadTypeForItem", "function duplicatorApplyStoredPin"]: - if helper_token in lifecycle_runtime: - fail(f"support helper still lives in item_lifecycle_runtime.js: {helper_token}") - for token in ["updateBall(dt, worldRef) { return window.TarinaiItemDynamicBallSystem", "attachPushpin(t, worldRef"]: - if token not in lifecycle_runtime: - fail(f"prototype subsystem facade missing: {token}") js = f""" const vm = require('vm'); const fs = require('fs'); @@ -1187,8 +1172,7 @@ context.window = context; context.isServingFoodType = (type) => type === 'food'; context.passiveFoodDecayInterval = () => 0.5; context.passiveFoodDecayRate = () => 0.1; -context.PASSIVE_FOOD_HYGIENE_PENALTY_PER_SERVING = 0.055; -context.TarinaiItemRegistry = {{ food: {{ hygienePenalty() {{ return 0.05; }} }} }}; +context.TarinaiItemRegistry = {{ food: {{ }} }}; context.isPinType = (type) => type === 'pushpin'; context.normalizeGrassStage = (item) => {{ item.normalized = true; }}; context.stableUnit = () => 0.5; @@ -1196,17 +1180,15 @@ vm.createContext(context); for (const file of {required!r}) {{ vm.runInContext(fs.readFileSync(file, 'utf8'), context, {{ filename: file }}); }} -if (!context.TarinaiItemLifecyclePipeline.canRunConcretePipeline()) throw new Error('item pipeline cannot run concrete steps'); const events = []; const world = {{ itemDropImpact(item) {{ events.push(['drop', item.type]); }}, - registerFoodSpoilage(value, item) {{ events.push(['spoil', value, item.type]); }}, markTerrainDirty(reason) {{ events.push(['terrain', reason]); }}, emit(type, payload) {{ events.push(['emit', type, payload.type]); }}, }}; const food = {{ type: 'food', dead: false, age: 0, dropTimer: 0.1, dropImpactDone: false, foodServingsRemaining: 1, amount: 1, passiveFoodDecayTimer: 0 }}; if (!context.TarinaiItemLifecyclePipeline.updateOne(food, 0.6, world, {{ legacyUpdate() {{ throw new Error('legacy should not run'); }} }})) throw new Error('food update failed'); -if (food.age !== 0.6 || !food.dropImpactDone || !(food.foodServingsRemaining < 1) || !events.some(([kind]) => kind === 'spoil')) throw new Error('frame/decay step did not run'); +if (food.age !== 0.6 || !food.dropImpactDone || !(food.foodServingsRemaining < 1) || !events.some(([kind, value]) => kind === 'terrain' && value === 'food-passive-decay')) throw new Error('frame/decay step did not run'); context.CONFIG = {{ worldPadding: 30 }}; context.clamp = (v, min, max) => Math.max(min, Math.min(max, v)); context.distXY = (ax, ay, bx, by) => Math.hypot(ax - bx, ay - by); @@ -1219,7 +1201,7 @@ context.TarinaiItemLifecyclePipeline.updateOne(dynamic, 0.25, ballWorld, {{ lega if (!(dynamic.x > 80) || !(dynamic.spin > 0)) throw new Error('dynamic step did not run'); const grass = {{ type: 'grass', dead: false, age: 0 }}; context.TarinaiItemLifecyclePipeline.updateOne(grass, 0.1, world, {{ legacyUpdate() {{ throw new Error('legacy should not run'); }} }}); -if (!grass.normalized) throw new Error('growth step did not run'); +if (grass.normalized) throw new Error('grass should not be normalized by per-item growth step'); """ with tempfile.NamedTemporaryFile("w", encoding="utf-8", suffix=".js", delete=False) as f: f.write(js) diff --git a/service-worker.js b/service-worker.js index 7bcd738..1a9deb2 100644 --- a/service-worker.js +++ b/service-worker.js @@ -1,11 +1,11 @@ "use strict"; -const APP_VERSION = "39.15.63"; +const APP_VERSION = "39.15.65"; 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. const coreScriptNames = [ - "version", "event_bus", "registry_base", "disease_registry", "sound_pack", "deterministic_helpers", "item_tool_definitions", "item_visual_definitions", "item_food_definitions", "item_effect_definitions", "item_registry", "data", "ground_types", "input_mode_manager", "math", "collision_footprint_system", "physics_helpers", "placement_collision_system", "placement_preview_system", "pin_attachment_system", "physics_shape_editor_system", "mechanical_system", "mechanical_shape_bridge", "constraint_system", "physics_world_system", "physics_projection_system", "assets", "audio", "perf_profiler", "render", "sim_core", "tarinai_seed_factory", "structures", "items", "item_type_initializers", "item_lifecycle_support", "item_lifecycle_runtime", "item_update_policy", "item_dynamic_tool_system", "item_dynamic_ball_system", "item_dynamic_duplicator_system", "item_dynamic_pin_system", "item_dynamic_zunchi_system", "item_dynamic_system", "item_lifecycle_decay_system", "item_lifecycle_growth_system", "item_lifecycle_step_frame", "item_lifecycle_step_decay", "item_lifecycle_step_dynamic", "item_lifecycle_step_growth", "item_lifecycle_pipeline", "item_runtime", "structure_lifecycle", "item_render_runtime", "ants", "health", "tarinai", "tarinai_action_spec", "tarinai_behavior_state", "tarinai_identity_social", "tarinai_action_state", "tarinai_disease_nest", "tarinai_item_effects", "tarinai_needs_core", "tarinai_behavior_text", "tarinai_forced_behavior", "tarinai_nest_sleep_system", "tarinai_item_targeting", "tarinai_consumable_behavior", "tarinai_social_action_runtime", "tarinai_building_behavior", "tarinai_action_definitions", "tarinai_needs_items", "tarinai_forced_action_planner_system", "tarinai_action_planner_system", "tarinai_need_planner_system", "tarinai_item_interaction_context", "tarinai_food_interaction_system", "tarinai_contact_item_system", "tarinai_item_interaction_system", "tarinai_sunbath_system", "tarinai_cursor_care_system", "tarinai_local_environment_system", "tarinai_social_move_life", "tarinai_update_step_frame", "tarinai_update_step_ai", "tarinai_update_step_environment", "tarinai_update_step_movement", "tarinai_update_step_health", "tarinai_update_pipeline", "tarinai_runtime", "tarinai_render", "world", "world_view", "family_graph", "world_family_social", "impact_core_system", "impact_response_system", "collision_response_system", "world_combat_effects", "world_environment", "world_spatial_budget", "world_ants_system", "weather_system", "item_update_scheduler", "simulation_runtime_helpers", "tarinai_update_policy", "simulation_environment_system", "simulation_item_ant_system", "simulation_effects_system", "simulation_creature_system", "simulation_maintenance_system", "simulation_ambient_system", "simulation_systems", "world_update_phases", "system_order", "simulation", "colony_situation_system", "world_update", "world_tool_actions", "world_placement_log", "world_event_effects", "command_dispatcher", "text_catalog", "ui", "ui_log", "ui_helpers", "ui_selected", "save_schema", "snapshot_system", "restore_coordinator", "history_system", "save_codec", "save_storage", "save_system", "patch_helpers", "ui_tooltips", "ui_layout_dialogs", "ui_ground", "ui_tools", "ui_input_shared", "ui_pointer_action_system", "ui_input_touch", "ui_input_mouse", "ui_bind", "ui_charts", "ui_family_data", "ui_family_async", "ui_family_layout", "ui_family_paths", "ui_family_render", "main", "debug_tools" + "version", "event_bus", "registry_base", "disease_registry", "sound_pack", "deterministic_helpers", "item_tool_definitions", "item_visual_definitions", "item_food_definitions", "item_effect_definitions", "item_registry", "data", "ground_types", "input_mode_manager", "math", "collision_footprint_system", "physics_helpers", "placement_preview_system", "pin_attachment_system", "physics_shape_editor_system", "mechanical_system", "mechanical_shape_bridge", "constraint_system", "physics_world_system", "physics_projection_system", "assets", "audio", "perf_profiler", "render", "sim_core", "tarinai_seed_factory", "structures", "items", "item_type_initializers", "item_lifecycle_support", "item_update_policy", "item_dynamic_tool_system", "item_dynamic_ball_system", "item_dynamic_duplicator_system", "item_dynamic_pin_system", "item_dynamic_zunchi_system", "item_dynamic_system", "item_lifecycle_decay_system", "item_lifecycle_growth_system", "item_lifecycle_step_frame", "item_lifecycle_step_decay", "item_lifecycle_step_dynamic", "item_lifecycle_step_growth", "item_lifecycle_pipeline", "item_runtime", "structure_lifecycle", "item_render_runtime", "ants", "health", "tarinai", "tarinai_action_spec", "tarinai_behavior_state", "tarinai_identity_social", "tarinai_action_state", "tarinai_disease_nest", "tarinai_item_effects", "tarinai_needs_core", "tarinai_behavior_text", "tarinai_forced_behavior", "tarinai_nest_sleep_system", "tarinai_item_targeting", "tarinai_consumable_behavior", "tarinai_social_action_runtime", "tarinai_building_behavior", "tarinai_action_definitions", "tarinai_needs_items", "tarinai_need_planner_system", "tarinai_item_interaction_context", "tarinai_food_interaction_system", "tarinai_contact_item_system", "tarinai_item_interaction_system", "tarinai_sunbath_system", "tarinai_cursor_care_system", "tarinai_local_environment_system", "tarinai_social_move_life", "tarinai_update_step_frame", "tarinai_update_step_ai", "tarinai_update_step_environment", "tarinai_update_step_movement", "tarinai_update_step_health", "tarinai_update_pipeline", "tarinai_runtime", "tarinai_render", "world", "world_view", "family_graph", "world_family_social", "impact_core_system", "impact_response_system", "collision_response_system", "world_combat_effects", "world_environment", "world_spatial_budget", "world_ants_system", "weather_system", "item_update_scheduler", "simulation_runtime_helpers", "tarinai_update_policy", "simulation_environment_system", "simulation_item_ant_system", "simulation_effects_system", "simulation_creature_system", "simulation_maintenance_system", "simulation_ambient_system", "simulation_systems", "system_order", "simulation", "colony_situation_system", "world_update", "world_tool_actions", "world_placement_log", "world_event_effects", "command_dispatcher", "text_catalog", "ui", "ui_log", "ui_helpers", "ui_selected", "save_schema", "snapshot_system", "restore_coordinator", "history_system", "save_codec", "save_storage", "save_system", "ui_tooltips", "ui_layout_dialogs", "ui_ground", "ui_tools", "ui_input_shared", "ui_pointer_action_system", "ui_input_touch", "ui_input_mouse", "ui_bind", "ui_charts", "ui_family_data", "ui_family_async", "ui_family_layout", "ui_family_paths", "ui_family_render", "main", "debug_tools" ]; const CORE_ASSETS = [ "./",