tarinai/scripts/ui_grass_limits_regression_audit.py
2026-07-18 13:06:02 +09:00

85 lines
5 KiB
Python

from pathlib import Path
import re
ROOT = Path(__file__).resolve().parents[1]
def text(rel):
return (ROOT / rel).read_text(encoding='utf-8')
def must(cond, msg):
if not cond:
raise AssertionError(msg)
index = text('index.html')
ui_bind = text('js/ui_bind.js')
ui_mouse = text('js/ui_input_mouse.js')
ui_shared = text('js/ui_input_shared.js')
view = text('js/world_view.js')
data = text('js/data.js')
temp = text('js/world_temperature_system.js')
disease = text('js/tarinai_item_effects.js')
spatial = text('js/sim_core.js')
spatial_budget = text('js/world_spatial_budget.js')
targeting = text('js/tarinai_item_targeting.js')
ach = text('js/achievements.js')
# Tool mode survives ordinary controls, but blank UI space explicitly clears it.
must('isInteractiveUiTarget' in ui_bind and 'clearSelectedToolSilently' in ui_bind,
'blank-UI tool cancellation helpers are missing')
must('!canvas?.contains?.(target) && !isInteractiveUiTarget(target)' in ui_bind,
'blank UI space is not the exclusive document-click cancellation path')
must('target.closest("button, a, input, select, textarea, label, summary' in ui_bind,
'ordinary UI controls are not protected from tool cancellation')
# Limits: 300, next slot 301 = infinity, default infinity.
must('COLONY_LIMIT_MAX = 300' in ui_bind, 'colony limit max is not 300')
must('COLONY_LIMIT_INFINITY_SLOT = 301' in ui_bind, 'infinity slot is not 301')
must(re.search(r'max="301"[^>]*value="301"', index) is not None, 'limit slider does not default to infinity slot')
# Right-drag context-menu suppression.
must('suppressContextMenuUntil' in ui_mouse and 'contextmenu' in ui_mouse, 'right-pan context-menu suppression missing')
must('suppressContextMenuUntil' in ui_shared, 'right-pan suppression timestamp is not set on pan end')
# Zoom.
must(re.search(r'BASE_ZOOM_MAX\s*=\s*4\.5', view) is not None, 'zoom maximum is not 4.5')
# Disease probability reduced to one-third at the shared helper.
must(re.search(r'Number\(base\)[^\n]*/\s*3', disease) is not None or '/ 3' in disease[disease.find('diseaseChance'):disease.find('diseaseChance')+240], 'diseaseChance is not reduced to one-third')
# Summer correction removed completely.
must(re.search(r'summerTemperatureBoost\s*:\s*0\b', data) is not None, 'summerTemperatureBoost is not zero')
body = re.search(r'(?:function\s+)?seasonTemperatureBias\s*\([^)]*\)\s*\{([\s\S]*?)\n\s*\}', temp)
must(body is not None and re.search(r'\breturn\s+0\s*;', body.group(1)), 'seasonTemperatureBias does not return zero')
# Grass: dedicated spatial index and no global grass fallback re-add in food search.
must('grassCells = new Map()' in spatial, 'dedicated grassCells index missing')
must('nearbyGrass(' in spatial_budget and 'spatial.grassCells' in spatial_budget, 'nearbyGrass spatial query missing')
must('type === "grass"' in targeting and 'nearbyFood' in targeting, 'grass global-bucket skip guard missing')
# Ensure the hot-path global type bucket explicitly skips grass when nearbyFood exists.
must(re.search(r'if\s*\(\s*type\s*===\s*["\']grass["\'][^)]*nearbyFood', targeting) is not None,
'food targeting does not skip the global grass bucket')
# Achievements 73-75 and persistent Memento Mori progress.
for aid in ('well_informed', 'lively_making', 'memento_mori'):
must(aid in ach, f'achievement {aid} missing')
must('memento_mori' in ach and 'PRE_UNLOCK_DESCRIPTION_IDS' in ach, 'Memento Mori pre-unlock condition visibility missing')
must('totalDeathCount' in ach, 'persistent total death counter missing')
# Current UI/achievement/colony changes.
placement_preview = text('js/placement_preview_system.js')
placement_log = text('js/world_placement_log.js')
colony = text('js/colony_situation_system.js')
components = text('css/components.css')
must('achievementToastCondition' in index and 'achievement-toast-condition' in components, 'achievement unlock toast condition line missing')
must('if (dom.toastCondition) dom.toastCondition.textContent = definition.description || "";' in ach, 'achievement toast does not render the completion condition')
must('lively_making' in ach and 'PRE_UNLOCK_DESCRIPTION_IDS' in ach and 'manualTarinaiAddedCount' in ach, 'Lively Making persistent/pre-unlock progress is missing')
must(r'title: "\u7e41\u6b96\u30fb\u4eba\u53e3"' in ach and r'title: "\u64cd\u4f5c"' in ach and 'id: "other"' in ach, 'six-category achievement reorganization missing')
must('num(rect.bottom) > (worldRef.h || 0)' in placement_preview, 'placement preview still reserves an artificial bottom padding')
must('rect.bottom > this.h' in placement_log, 'confirmed placement still reserves an artificial bottom padding')
must('Math.max(5, Math.ceil(m.n * 0.10))' in colony, 'crisis mortality threshold is still too loose')
must('criticalRatio' in colony and 'crisisMortalitySignal && crisisSystemicStress' in colony and 'crisisCollapseSignal' in colony, 'crisis no longer requires compound severe conditions')
must('!m.populationIncreasing' in colony, 'crisis population-growth exclusion was lost')
print('[OK] UI/limits/input/zoom/disease/grass/summer/achievement/placement/crisis regression audit passed')