85 lines
5 KiB
Python
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')
|