2026-06-24 13:48:22 +09:00
#!/usr/bin/env python3
""" Lightweight regression guard for the buildless Tarinai app.
Checks static - file consistency , generated - load order , syntax , and the ActionSpec
registry contract . This intentionally avoids save - data migration checks ; save
2026-06-25 14:51:58 +09:00
compatibility is intentionally not retained for save format changes .
2026-06-24 13:48:22 +09:00
"""
from __future__ import annotations
from pathlib import Path
import json
import re
import subprocess
import sys
import tempfile
ROOT = Path ( __file__ ) . resolve ( ) . parents [ 1 ]
MANIFEST = ROOT / " app_manifest.json "
def fail ( message : str ) - > None :
print ( f " [FAIL] { message } " , file = sys . stderr )
raise SystemExit ( 1 )
def ok ( message : str ) - > None :
print ( f " [OK] { message } " )
def read_text ( path : Path ) - > str :
return path . read_text ( encoding = " utf-8 " )
def manifest ( ) - > dict :
try :
data = json . loads ( read_text ( MANIFEST ) )
except Exception as exc : # pragma: no cover - diagnostic path
fail ( f " cannot read app_manifest.json: { exc } " )
for key in ( " version " , " css " , " js " ) :
if key not in data :
fail ( f " manifest missing { key } " )
return data
def check_files_exist ( data : dict ) - > None :
missing = [ p for p in [ * data [ " css " ] , * data [ " js " ] ] if not ( ROOT / p ) . exists ( ) ]
if missing :
fail ( " manifest references missing files: " + " , " . join ( missing ) )
ok ( " manifest file references exist " )
def check_versions ( data : dict ) - > None :
version = str ( data [ " version " ] )
version_js = read_text ( ROOT / " js/version.js " )
service_worker = read_text ( ROOT / " service-worker.js " )
if f ' APP_VERSION = " { version } " ' not in version_js :
fail ( " js/version.js APP_VERSION does not match manifest " )
if f ' APP_VERSION = " { version } " ' not in service_worker :
fail ( " service-worker.js APP_VERSION does not match manifest " )
ok ( f " version synchronized: { version } " )
def check_index_order ( data : dict ) - > None :
index = read_text ( ROOT / " index.html " )
css = re . findall ( r ' <link rel= " stylesheet " href= " ([^ " ]+) " \ s*/> ' , index )
scripts = re . findall ( r ' <script src= " ([^ " ]+) " defer></script> ' , index )
css_clean = [ entry . split ( " ? " , 1 ) [ 0 ] for entry in css if entry . startswith ( " css/ " ) ]
script_clean = [ entry . split ( " ? " , 1 ) [ 0 ] for entry in scripts if entry . startswith ( " js/ " ) ]
if css_clean [ : len ( data [ " css " ] ) ] != data [ " css " ] :
fail ( " index.html CSS order differs from manifest " )
if script_clean [ : len ( data [ " js " ] ) ] != data [ " js " ] :
fail ( " index.html JS order differs from manifest " )
ok ( " index.html load order matches manifest " )
def check_service_worker_order ( data : dict ) - > None :
sw = read_text ( ROOT / " service-worker.js " )
match = re . search ( r " const coreScriptNames = \ [ \ s*(.*?) \ s* \ ]; " , sw , re . S )
if not match :
fail ( " service-worker.js coreScriptNames not found " )
names = re . findall ( r ' " ([^ " ]+) " ' , match . group ( 1 ) )
expected = [ Path ( p ) . stem for p in data [ " js " ] ]
if names != expected :
fail ( " service-worker.js coreScriptNames differs from manifest JS order " )
ok ( " service-worker.js core script order matches manifest " )
def node_check ( paths : list [ Path ] ) - > None :
node = " node "
for path in paths :
result = subprocess . run ( [ node , " --check " , str ( path ) ] , cwd = ROOT , text = True , capture_output = True )
if result . returncode != 0 :
sys . stderr . write ( result . stdout )
sys . stderr . write ( result . stderr )
fail ( f " node --check failed: { path . relative_to ( ROOT ) } " )
ok ( f " node --check passed for { len ( paths ) } files " )
def action_spec_smoke ( ) - > None :
source = read_text ( ROOT / " js/tarinai_action_spec.js " )
js = f """
const vm = require ( ' vm ' ) ;
const context = { { console , globalThis : null } } ;
context . globalThis = context ;
context . window = context ;
vm . createContext ( context ) ;
vm . runInContext ( { source ! r } , context , { { filename : ' tarinai_action_spec.js ' } } ) ;
const action = context . registerTarinaiActionSpec ( { {
id : ' smoke_action ' ,
need : ' fulfill ' ,
label : ' smoke ' ,
selectTarget ( ) { { return { { id : ' target-1 ' } } ; } } ,
canStart ( t , world , ctx ) { { const target = ctx . target | | this . selectTarget ( t , world , ctx ) ; return target & & target . id == = ' target-1 ' ; } } ,
start ( t , world , ctx ) { { t . startedWith = ctx . target . id ; return true ; } } ,
tick ( ) { { return ' finished ' ; } } ,
finish ( t ) { { t . finished = true ; return true ; } } ,
} } ) ;
const subject = { { } } ;
if ( ! context . canStartTarinaiAction ( ' smoke_action ' , subject , { { } } ) ) throw new Error ( ' canStart failed ' ) ;
if ( ! context . startTarinaiAction ( action , subject , { { } } ) ) throw new Error ( ' start failed ' ) ;
if ( subject . startedWith != = ' target-1 ' ) throw new Error ( ' selectTarget/start context failed ' ) ;
if ( context . tickTarinaiAction ( ' smoke_action ' , subject , { { } } , 0.1 , { { } } ) != = ' finished ' ) throw new Error ( ' tick failed ' ) ;
if ( ! context . finishTarinaiAction ( ' smoke_action ' , subject , { { } } , { { phase : ' finished ' } } ) ) throw new Error ( ' finish failed ' ) ;
"""
with tempfile . NamedTemporaryFile ( " w " , encoding = " utf-8 " , suffix = " .js " , delete = False ) as f :
f . write ( js )
tmp = Path ( f . name )
try :
result = subprocess . run ( [ " node " , str ( tmp ) ] , cwd = ROOT , text = True , capture_output = True )
finally :
tmp . unlink ( missing_ok = True )
if result . returncode != 0 :
sys . stderr . write ( result . stdout )
sys . stderr . write ( result . stderr )
fail ( " ActionSpec smoke test failed " )
ok ( " ActionSpec lifecycle smoke test passed " )
def behavior_state_smoke ( ) - > None :
source = read_text ( ROOT / " js/tarinai_behavior_state.js " )
js = f """
const vm = require ( ' vm ' ) ;
const context = { { console , globalThis : null } } ;
context . globalThis = context ;
context . window = context ;
context . Tarinai = class Tarinai { { } } ;
vm . createContext ( context ) ;
vm . runInContext ( { source ! r } , context , { { filename : ' tarinai_behavior_state.js ' } } ) ;
const t = new context . Tarinai ( ) ;
t . world = { { time : 12 } } ;
t . state = ' seek_food ' ;
2026-06-25 14:51:58 +09:00
context . setTarinaiBehavior ( t , { { actionId : ' eat_food ' , need : ' food ' , label : ' eat ' , reason : ' hungry ' , text : ' hungry ' , tiedNeeds : [ ' food ' , ' sleep ' ] , source : ' forced ' , forcedId : ' f1 ' , forcedRequest : { { uid : ' f1 ' , id : ' eat_food ' , source : ' test ' , priority : 99 , reasonText : ' forced hungry ' , status : ' active ' } } } } ) ;
2026-06-24 13:48:22 +09:00
if ( context . getTarinaiBehaviorId ( t ) != = ' eat_food ' ) throw new Error ( ' behavior id mismatch ' ) ;
if ( context . getTarinaiBehaviorNeed ( t ) != = ' food ' ) throw new Error ( ' behavior need mismatch ' ) ;
2026-06-25 14:51:58 +09:00
if ( ! context . isTarinaiBehaviorForced ( t ) ) throw new Error ( ' forced state not visible ' ) ;
const behavior = context . currentTarinaiBehavior ( t ) ;
if ( ! behavior | | behavior . actionId != = ' eat_food ' | | behavior . reason != = ' hungry ' | | behavior . source != = ' forced ' ) throw new Error ( ' canonical behavior fields not normalized ' ) ;
if ( ' intent ' in t | | ' currentAction ' in t | | ' activeBehavior ' in t | | ' activeForcedBehavior ' in t ) throw new Error ( ' legacy behavior accessors still present ' ) ;
2026-06-24 13:48:22 +09:00
const forced = context . getTarinaiForcedBehavior ( t ) ;
if ( ! forced | | forced . uid != = ' f1 ' | | forced . priority != = 99 ) throw new Error ( ' forced request not normalized ' ) ;
context . setTarinaiForcedBehavior ( t , null ) ;
2026-06-25 14:51:58 +09:00
if ( context . currentTarinaiBehavior ( t ) . source == = ' forced ' | | context . getTarinaiForcedBehavior ( t ) ) throw new Error ( ' forced source not cleared ' ) ;
2026-06-24 13:48:22 +09:00
context . clearTarinaiBehavior ( t ) ;
if ( context . currentTarinaiBehavior ( t ) != = null ) throw new Error ( ' behavior not cleared ' ) ;
"""
with tempfile . NamedTemporaryFile ( " w " , encoding = " utf-8 " , suffix = " .js " , delete = False ) as f :
f . write ( js )
tmp = Path ( f . name )
try :
result = subprocess . run ( [ " node " , str ( tmp ) ] , cwd = ROOT , text = True , capture_output = True )
finally :
tmp . unlink ( missing_ok = True )
if result . returncode != 0 :
sys . stderr . write ( result . stdout )
sys . stderr . write ( result . stderr )
fail ( " Behavior state smoke test failed " )
ok ( " behavior state smoke test passed " )
def check_legacy_behavior_refs ( ) - > None :
tokens = re . compile ( r " \ b(activeBehavior|currentAction|activeForcedBehavior|intent) \ b " )
offenders = [ ]
for path in ( ROOT / " js " ) . glob ( " *.js " ) :
for i , line in enumerate ( read_text ( path ) . splitlines ( ) , start = 1 ) :
2026-06-25 14:51:58 +09:00
stripped = line . strip ( )
if stripped . startswith ( " // " ) :
continue
if tokens . search ( line ) :
offenders . append ( f " { path . relative_to ( ROOT ) . as_posix ( ) } : { i } " )
2026-06-24 13:48:22 +09:00
if offenders :
2026-06-25 14:51:58 +09:00
fail ( " legacy behavior fields still present: " + " , " . join ( offenders [ : 20 ] ) )
ok ( " legacy behavior runtime fields removed " )
2026-06-24 13:48:22 +09:00
2026-06-25 14:51:58 +09:00
def check_basic_action_spec_runtime ( ) - > None :
source = " \n " . join ( read_text ( ROOT / rel ) for rel in [ " js/tarinai_action_definitions.js " , " js/tarinai_needs_items.js " ] )
2026-06-24 13:48:22 +09:00
required = {
" eat_food " : " createConsumableActionSpec " ,
" drink_water " : " createConsumableActionSpec " ,
" use_medicine " : " createConsumableActionSpec " ,
" sleep_in_bed " : " createSleepInBedActionSpec " ,
" sleep_build_grass_bed " : " createSleepBuildGrassBedActionSpec " ,
" sleep_anywhere " : " createSleepAnywhereActionSpec " ,
" rest_to_recover " : " createRestActionSpec " ,
" sunbath " : " createSunbathActionSpec " ,
" wander_lightly " : " createWanderActionSpec " ,
}
for action_id , factory in required . items ( ) :
if factory not in source :
fail ( f " basic ActionSpec factory missing for { action_id } : { factory } " )
forbidden_late_ticks = [
' set( " eat_food " , ' ,
' set( " drink_water " , ' ,
' set( " use_medicine " , ' ,
' set( " sunbath " , ' ,
]
offenders = [ token for token in forbidden_late_ticks if token in source ]
if offenders :
fail ( " basic actions still patched in configureTarinaiNeedActions: " + " , " . join ( offenders ) )
2026-06-25 14:51:58 +09:00
ok ( " basic need actions owned by ActionSpec factories " )
2026-06-24 13:48:22 +09:00
2026-06-25 14:51:58 +09:00
def check_social_action_spec_runtime ( ) - > None :
source = " \n " . join ( read_text ( ROOT / rel ) for rel in [ " js/tarinai_action_definitions.js " , " js/tarinai_needs_items.js " ] )
2026-06-24 13:48:22 +09:00
required_factories = [
" createPanicEscapeActionSpec " ,
" createFleeActionSpec " ,
" createApproachFriendActionSpec " ,
" createApproachFamilyActionSpec " ,
" createApproachMateActionSpec " ,
" createFightRivalActionSpec " ,
" createIntimidateEnemyActionSpec " ,
" createBirthRitualActionSpec " ,
]
for factory in required_factories :
if factory not in source :
fail ( f " social/combat ActionSpec factory missing: { factory } " )
definitions_match = re . search ( r " const TARINAI_ACTION_DEFINITIONS = \ [(.*?) \ ]; " , source , re . S )
if not definitions_match :
fail ( " TARINAI_ACTION_DEFINITIONS block missing " )
definitions = definitions_match . group ( 1 )
for factory in required_factories :
if f " { factory } () " not in definitions :
fail ( f " { factory } is not registered in TARINAI_ACTION_DEFINITIONS " )
if " function configureTarinaiNeedActions " in source :
2026-06-25 14:51:58 +09:00
fail ( " configureTarinaiNeedActions should not be needed after ActionSpec runtime consolidation " )
2026-06-24 13:48:22 +09:00
forbidden_late_ticks = [
' set( " panic_escape " , ' ,
' set( " flee " , ' ,
' set( " approach_friend " , ' ,
' set( " approach_parent_or_child " , ' ,
' set( " approach_mate " , ' ,
' set( " fight_rival " , ' ,
' set( " intimidate_enemy " , ' ,
' set( " sunbath " , ' ,
" birthRitualAction " ,
" TARINAI_ACTIONS.push " ,
]
offenders = [ token for token in forbidden_late_ticks if token in source ]
if offenders :
fail ( " actions still patched late: " + " , " . join ( offenders ) )
2026-06-25 14:51:58 +09:00
ok ( " social/fear/combat/mate/birth/sunbath actions owned by ActionSpec factories " )
def check_action_spec_no_legacy_hooks ( ) - > None :
source = read_text ( ROOT / " js/tarinai_action_spec.js " )
forbidden = [ " condition " , " originalRun " , " originalUpdate " , " Legacy hooks " ]
offenders = [ token for token in forbidden if token in source ]
if offenders :
fail ( " ActionSpec legacy hook support still present: " + " , " . join ( offenders ) )
needs = " \n " . join ( read_text ( ROOT / rel ) for rel in [ " js/tarinai_action_definitions.js " , " js/tarinai_consumable_behavior.js " , " js/tarinai_social_action_runtime.js " , " js/tarinai_building_behavior.js " , " js/tarinai_needs_items.js " ] )
if " .condition " in needs or " action.condition " in needs :
fail ( " need action runtime still falls back to action.condition " )
ok ( " ActionSpec legacy hooks removed " )
2026-06-24 13:48:22 +09:00
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 " )
2026-06-25 14:51:58 +09:00
world_update_phases = read_text ( ROOT / " js/world_update_phases.js " )
2026-06-26 12:46:32 +09:00
simulation_systems = read_text ( ROOT / " js/simulation_systems.js " )
2026-06-24 13:48:22 +09:00
if " runtimeDiagnostics() " not in spatial_budget :
fail ( " World.runtimeDiagnostics hook missing " )
2026-06-26 12:46:32 +09:00
if " lastRuntimeDiagnostics " not in ( world_update + world_update_phases + simulation_systems ) :
2026-06-25 14:51:58 +09:00
fail ( " world update phases do not publish lastRuntimeDiagnostics " )
2026-06-24 13:48:22 +09:00
if " spatial rebuild " not in debug_tools or " behavior forced " not in debug_tools :
fail ( " debug overlay is missing runtime diagnostic lines " )
2026-06-25 14:51:58 +09:00
spatial_budget = read_text ( ROOT / " js/world_spatial_budget.js " )
debug_tools = read_text ( ROOT / " js/debug_tools.js " )
if " performance: " in spatial_budget or " diag.performance " in debug_tools or " load-check removed " in debug_tools :
fail ( " removed performance/load-check diagnostics residue remains " )
2026-06-24 13:48:22 +09:00
ok ( " runtime diagnostics hooks present " )
def check_social_balance_guards ( ) - > None :
2026-06-25 14:51:58 +09:00
needs = " \n " . join ( read_text ( ROOT / rel ) for rel in [
" js/tarinai_needs_core.js " ,
" js/tarinai_behavior_text.js " ,
" js/tarinai_forced_behavior.js " ,
" js/tarinai_item_targeting.js " ,
" js/tarinai_action_definitions.js " ,
" js/tarinai_consumable_behavior.js " ,
" js/tarinai_social_action_runtime.js " ,
" js/tarinai_building_behavior.js " ,
" js/tarinai_needs_items.js " ,
] )
2026-06-24 13:48:22 +09:00
world_social = read_text ( ROOT / " js/world_family_social.js " )
social_move = read_text ( ROOT / " js/tarinai_social_move_life.js " )
required_need_tokens = [
" fight_rival: 56 " ,
" intimidate_enemy: 42 " ,
" approach_mate: 68 " ,
" approach_friend: 66 " ,
" return ( { conflict: 34, mate: 24, family: 12, bond: 14 " ,
" if (subNeed !== \" conflict \" && subValue >= threshold) return true " ,
" subValue >= threshold && subValue >= socialPull * 0.82 " ,
]
for token in required_need_tokens :
if token not in needs :
fail ( f " social balance guard token missing: { token } " )
if " canStart: basicCanStartWithTarget, \n start(t, world, ctx = {} ) { \n const rival " in needs :
fail ( " intimidate_enemy reverted to ungated basicCanStart " )
required_world_tokens = [
" fightPairCooldowns " ,
" fightPairKey(a, b) " ,
" fightPairCooldownRemaining(a, b) " ,
" markFightPairCooldown(a, b " ,
" isAlreadyFightingPair(a, b) " ,
" if (this.isAlreadyFightingPair?.(a, b)) return true " ,
]
for token in required_world_tokens :
if token not in world_social :
fail ( f " fight re-entry guard missing: { token } " )
if " dt * (battleDrug ? 0.23 : 0.085) " not in social_move :
fail ( " ordinary proximity fight probability guard missing " )
ok ( " social balance and fight re-entry guards present " )
def check_item_bucket_polish ( ) - > None :
spatial_budget = read_text ( ROOT / " js/world_spatial_budget.js " )
required_tokens = [
" markItemBucketsDirty(reason " ,
" ensureItemBuckets(reason " ,
" addItem(item " ,
" itemById(id) " ,
" itemsOfType(type) " ,
" liveItemsOfType(type " ,
" this.itemIdMap = new Map() " ,
" bucketRebuildsTotal " ,
]
for token in required_tokens :
if token not in spatial_budget :
fail ( f " item bucket polish helper missing: { token } " )
if " return item && !item.dead ? item : null " not in spatial_budget :
fail ( " itemById must not return dead items " )
debug_tools = read_text ( ROOT / " js/debug_tools.js " )
if " bucket " not in debug_tools . lower ( ) and " id-map " not in debug_tools . lower ( ) :
fail ( " debug overlay is missing item bucket/id-map diagnostics " )
routed_files = {
" js/world_placement_log.js " : " addItem?.(item " ,
2026-06-26 12:46:32 +09:00
" js/simulation_ambient_system.js " : " addItem?.(drop " ,
2026-06-24 13:48:22 +09:00
" js/world_combat_effects.js " : " addItem?.(it " ,
" js/ants.js " : " addItem?.(corpse " ,
" js/tarinai_social_move_life.js " : " addItem?.(new Item( \" trace \" " ,
2026-06-25 14:51:58 +09:00
" js/tarinai_building_behavior.js " : " addItem?.(structure " ,
2026-06-24 13:48:22 +09:00
}
for rel , token in routed_files . items ( ) :
if token not in read_text ( ROOT / rel ) :
fail ( f " runtime item creation path is not routed through addItem: { rel } " )
ok ( " runtime item bucket/id-map polish present " )
2026-06-25 14:51:58 +09:00
def check_current_save_format ( ) - > None :
2026-06-24 13:48:22 +09:00
snapshot = read_text ( ROOT / " js/snapshot_system.js " )
save = read_text ( ROOT / " js/save_system.js " )
2026-06-25 14:51:58 +09:00
save_codec = read_text ( ROOT / " js/save_codec.js " )
save_storage = read_text ( ROOT / " js/save_storage.js " )
restore_coordinator = read_text ( ROOT / " js/restore_coordinator.js " )
2026-06-24 13:48:22 +09:00
required_snapshot = [
2026-06-26 19:04:32 +09:00
" const SNAPSHOT_VERSION = SaveSchema.SNAPSHOT_VERSION " ,
2026-06-25 17:43:24 +09:00
" a: \" tj1 \" " ,
2026-06-24 13:48:22 +09:00
" function compactWorld " ,
" function compactTarinai " ,
" function compactItem " ,
2026-06-25 17:43:24 +09:00
" worldRef.logs = [] " ,
2026-06-24 13:48:22 +09:00
" worldRef.family = {} " ,
" recordFamily " ,
]
for token in required_snapshot :
if token not in snapshot :
2026-06-25 17:43:24 +09:00
fail ( f " binary save snapshot token missing: { token } " )
2026-06-24 13:48:22 +09:00
forbidden_snapshot = [
2026-06-25 17:43:24 +09:00
" compactAnt " ,
2026-06-24 13:48:22 +09:00
" effects: (worldRef.effects " ,
2026-06-25 17:43:24 +09:00
" cameraX " ,
" cameraY " ,
" behavior: " ,
" deathReason " ,
2026-06-24 13:48:22 +09:00
" summary: " ,
" app: \" tarinai_colony_game \" " ,
]
offenders = [ token for token in forbidden_snapshot if token in snapshot ]
if offenders :
2026-06-25 17:43:24 +09:00
fail ( " broad snapshot fields still present after light-save conversion: " + " , " . join ( offenders ) )
2026-06-25 14:51:58 +09:00
required_codec = [
2026-06-25 17:43:24 +09:00
' EXPORT_PREFIX = " たり " ' ,
' RAW_CODEC = " 生 " ' ,
' DEFLATE_CODEC = " 縮 " ' ,
2026-06-24 13:48:22 +09:00
" SAVE_TEXT_ALPHABET " ,
2026-06-25 17:43:24 +09:00
" jp2048Encode " ,
" jp2048Decode " ,
2026-06-24 13:48:22 +09:00
' CompressionStream( " deflate-raw " ) ' ,
" DecompressionStream " ,
" async function encodeSnapshot " ,
" async function decodeSnapshot " ,
]
2026-06-25 14:51:58 +09:00
for token in required_codec :
if token not in save_codec :
2026-06-25 17:43:24 +09:00
fail ( f " binary save codec token missing: { token } " )
2026-06-25 14:51:58 +09:00
required_storage = [
2026-06-25 17:43:24 +09:00
' STORAGE_PREFIX = " tarinai_japanese_hash_slot_v1_ " ' ,
2026-06-25 14:51:58 +09:00
" function writeSlot " ,
" function readSlot " ,
" function requireSlot " ,
" function deleteSlot " ,
]
for token in required_storage :
if token not in save_storage :
2026-06-25 17:43:24 +09:00
fail ( f " binary save storage token missing: { token } " )
2026-06-25 14:51:58 +09:00
required_facade = [
" TarinaiSaveSystem " ,
" TarinaiSaveCodec " ,
" TarinaiSaveStorage " ,
" TarinaiRestoreCoordinator " ,
]
combined = save + save_codec + save_storage + restore_coordinator
for token in required_facade :
if token not in combined :
fail ( f " save/restore split facade token missing: { token } " )
2026-06-25 17:43:24 +09:00
for token in ( " TN2! " , " TN3! " , " tarinai_save_slot_v2_ " , " tarinai_save_slot_v3_ " , " legacyForcedColumn " ) :
2026-06-25 14:51:58 +09:00
if token in combined or token in snapshot :
fail ( f " save backward compatibility residue still present: { token } " )
if " renderStats " in snapshot or " TarinaiFreezeSystem " in snapshot or " renderArchive " in snapshot :
fail ( " snapshot restore must not call UI or freeze subsystems directly " )
effects = read_text ( ROOT / " js/world_event_effects.js " )
2026-06-26 19:04:32 +09:00
if " tool:placed " not in effects or " function playLogSoundForEntry " not in effects :
fail ( " event effects do not own tool/log side effects " )
for rel in ( " js/freeze_system.js " , " js/freeze_store.js " , " js/freeze_snapshot_adapter.js " , " js/freeze_panel.js " ) :
if ( ROOT / rel ) . exists ( ) :
fail ( f " freezer subsystem file still exists after freezer removal: { rel } " )
2026-06-25 14:51:58 +09:00
placement = read_text ( ROOT / " js/world_placement_log.js " )
if " playLogSoundForEntry " in placement :
fail ( " log sound mapping still lives in world_placement_log.js " )
2026-06-25 17:43:24 +09:00
ok ( " binary hash save snapshot, codec, storage, and restore coordinator present " )
2026-06-25 14:51:58 +09:00
def check_unified_item_registry ( ) - > None :
data = manifest ( )
js_files = data [ " js " ]
if " js/item_registry.js " not in js_files :
fail ( " unified item registry missing from manifest " )
removed = [ " js/food_registry.js " , " js/effect_registry.js " , " js/item_visual_registry.js " ]
stale_manifest = [ rel for rel in removed if rel in js_files ]
if stale_manifest :
fail ( " old split item registry files still listed in manifest: " + " , " . join ( stale_manifest ) )
stale_files = [ rel for rel in removed if ( ROOT / rel ) . exists ( ) ]
if stale_files :
fail ( " old split item registry files still exist: " + " , " . join ( stale_files ) )
source = read_text ( ROOT / " js/item_registry.js " )
required = [
" global.TarinaiItemRegistry " ,
" const TOOL_DEFINITIONS " ,
" function itemDefinition " ,
" function itemVisualDefinition " ,
" class FoodRegistry " ,
" class EffectRegistry " ,
" food: FOOD_REGISTRY " ,
" effect: EFFECT_REGISTRY " ,
" visual: itemVisualDefinition " ,
]
for token in required :
if token not in source :
fail ( f " unified item registry token missing: { token } " )
forbidden_globals = [ " global.TarinaiFoodRegistry " , " global.TarinaiEffectRegistry " , " global.TARINAI_ITEM_VISUALS " , " global.TARINAI_FOOD_DEFINITIONS " , " global.TARINAI_EFFECT_DEFINITIONS " ]
offenders = [ token for token in forbidden_globals if token in source ]
if offenders :
fail ( " old split item registry globals still exported: " + " , " . join ( offenders ) )
data_source = read_text ( ROOT / " js/data.js " )
forbidden_data = [ " const TOOL_DEFINITIONS " , " const ITEM_TRAITS " , " class FoodRegistry " , " class EffectRegistry " , " TARINAI_ITEM_VISUALS " ]
offenders = [ token for token in forbidden_data if token in data_source ]
if offenders :
fail ( " item definitions still mixed into data.js: " + " , " . join ( offenders ) )
ok ( " item definitions centralized in item_registry.js " )
def check_docs_removed ( ) - > None :
docs = ROOT / " docs "
if docs . exists ( ) and any ( docs . iterdir ( ) ) :
fail ( " non-runtime docs directory still contains files " )
ok ( " non-runtime docs removed " )
def check_phase_and_input_split ( ) - > None :
data = manifest ( )
js_files = data [ " js " ]
required = [
2026-06-26 12:46:32 +09:00
" js/simulation_runtime_helpers.js " ,
" js/simulation_environment_system.js " ,
" js/simulation_item_ant_system.js " ,
" js/simulation_effects_system.js " ,
" js/simulation_creature_system.js " ,
" js/simulation_maintenance_system.js " ,
" js/simulation_ambient_system.js " ,
" js/simulation_systems.js " ,
2026-06-25 14:51:58 +09:00
" js/world_update_phases.js " ,
" js/ui_input_shared.js " ,
" js/ui_input_touch.js " ,
" js/ui_input_mouse.js " ,
]
for rel in required :
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 " )
2026-06-26 12:46:32 +09:00
systems = read_text ( ROOT / " js/simulation_systems.js " )
if " TarinaiSimulation " not in world_update or " function update(worldRef, dt) " not in phases :
2026-06-25 14:51:58 +09:00
fail ( " World.update is not delegated to world_update_phases.js " )
for token in [ " updateClock " , " updateEnvironment " , " updateItemsAndAnts " , " updateCreatures " , " runMaintenance " , " spawnRainWater " ] :
2026-06-26 12:46:32 +09:00
if token not in phases or token not in systems :
2026-06-25 14:51:58 +09:00
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 " )
for token in [ " TarinaiUIInputShared " , " createInputContext " ] :
if token not in shared :
fail ( f " shared input helper missing: { token } " )
if " touchstart " not in touch or " TarinaiTouchInput " not in touch :
fail ( " touch input module missing touch handlers " )
if " mousedown " not in mouse or " TarinaiMouseInput " not in mouse :
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
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 :
fail ( " birth log sound can double-fire; direct audio.birth remains in log-emitting birth flows " )
ok ( " world phase split and UI input split present " )
def check_processing_split ( ) - > None :
data = manifest ( )
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 " ,
" js/tarinai_consumable_behavior.js " ,
" js/tarinai_social_action_runtime.js " ,
" js/tarinai_building_behavior.js " ,
]
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/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 " updateDuplicator " in items or " \n draw(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 " )
consumable = read_text ( ROOT / " js/tarinai_consumable_behavior.js " )
social = read_text ( ROOT / " js/tarinai_social_action_runtime.js " )
building = read_text ( ROOT / " js/tarinai_building_behavior.js " )
needs = read_text ( ROOT / " js/tarinai_needs_items.js " )
required_pairs = [
( defs , " const TARINAI_ACTION_DEFINITIONS " ) ,
( defs , " const TARINAI_ACTIONS " ) ,
( consumable , " function updateConsumableBehavior " ) ,
( social , " function updateMateBehavior " ) ,
( social , " function updatePanicBehavior " ) ,
( building , " function continueBuildPlan " ) ,
( needs , " function chooseActionForNeed " ) ,
]
for source , token in required_pairs :
if token not in source :
fail ( f " processing split token missing: { token } " )
for token in [ " createConsumableActionSpec " , " function updateConsumableBehavior " , " function continueBuildPlan " ] :
if token in needs :
fail ( f " tarinai_needs_items.js still owns extracted processing: { token } " )
ok ( " item and Tarinai processing split present " )
2026-06-24 13:48:22 +09:00
2026-06-26 12:46:32 +09:00
def command_dispatcher_smoke ( ) - > None :
source = read_text ( ROOT / " js/command_dispatcher.js " )
js = f """
const vm = require ( ' vm ' ) ;
const context = { { console , globalThis : null } } ;
context . globalThis = context ;
context . window = context ;
context . World = class World { { } } ;
context . scalableToolIds = ( ) = > [ ' grass ' , ' sweet ' ] ;
context . normalizedToolSizeFor = ( world , tool ) = > ( world . toolSizes & & world . toolSizes [ tool ] ) | | world . toolSize | | ' medium ' ;
vm . createContext ( context ) ;
vm . runInContext ( { source ! r } , context , { { filename : ' command_dispatcher.js ' } } ) ;
const events = [ ] ;
const world = new context . World ( ) ;
world . tool = ' observe ' ;
world . toolSize = ' medium ' ;
world . toolSizes = { { } } ;
world . speed = 1 ;
world . paused = false ;
world . w = 1000 ;
world . h = 700 ;
world . viewportW = 500 ;
world . viewportH = 300 ;
world . clampCamera = ( ) = > { { world . clamped = true ; } } ;
world . emit = ( type , payload ) = > events . push ( [ type , payload ] ) ;
world . handleClick = ( x , y ) = > { { world . clicked = [ x , y ] ; } } ;
const target = { { x : 420 , y : 260 , dead : false } } ;
if ( ! context . TarinaiCommands . dispatch ( world , { { type : ' tool.select ' , toolId : ' grass ' } } ) . ok | | world . tool != = ' grass ' ) throw new Error ( ' tool select failed ' ) ;
if ( ! context . TarinaiCommands . dispatch ( world , { { type : ' tool.size.cycle ' , toolId : ' grass ' , order : [ ' small ' , ' medium ' , ' large ' ] } } ) . ok | | world . toolSize != = ' large ' ) throw new Error ( ' tool size failed ' ) ;
if ( ! context . TarinaiCommands . dispatch ( world , { { type : ' selection.focus ' , target } } ) . ok | | world . selected != = target | | world . cameraX != = 170 | | world . cameraY != = 110 ) throw new Error ( ' focus failed ' ) ;
if ( ! context . TarinaiCommands . dispatch ( world , { { type : ' selection.favorite.toggle ' } } ) . ok | | target . favorite != = true ) throw new Error ( ' favorite failed ' ) ;
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 ( ! 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 :
f . write ( js )
tmp = Path ( f . name )
try :
result = subprocess . run ( [ " node " , str ( tmp ) ] , cwd = ROOT , text = True , capture_output = True )
finally :
tmp . unlink ( missing_ok = True )
if result . returncode != 0 :
sys . stderr . write ( result . stdout )
sys . stderr . write ( result . stderr )
fail ( " command dispatcher smoke test failed " )
ok ( " command dispatcher smoke test passed " )
def check_system_order_boundary ( ) - > None :
data = manifest ( )
js_files = data [ " js " ]
required = [
" js/simulation_runtime_helpers.js " ,
" js/simulation_environment_system.js " ,
" js/simulation_item_ant_system.js " ,
" js/simulation_effects_system.js " ,
" js/simulation_creature_system.js " ,
" 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 " ,
]
for rel in required :
if rel not in js_files :
fail ( f " simulation boundary module missing from manifest: { rel } " )
split_order = [
" js/simulation_runtime_helpers.js " ,
" js/simulation_environment_system.js " ,
" js/simulation_item_ant_system.js " ,
" js/simulation_effects_system.js " ,
" js/simulation_creature_system.js " ,
" 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 " ,
]
if [ rel for rel in js_files if rel in split_order ] != split_order :
fail ( " simulation boundary load order is invalid " )
system_order = read_text ( ROOT / " js/system_order.js " )
simulation = read_text ( ROOT / " js/simulation.js " )
systems_source = read_text ( ROOT / " js/simulation_systems.js " )
if " TarinaiSimulationSystems " not in systems_source or " const phases = Object.freeze " not in systems_source :
fail ( " concrete simulation systems facade missing exported phase registry " )
concrete_modules = {
" js/simulation_runtime_helpers.js " : " TarinaiSimulationRuntime " ,
" js/simulation_environment_system.js " : " TarinaiEnvironmentSimulationSystem " ,
" js/simulation_item_ant_system.js " : " TarinaiItemAntSimulationSystem " ,
" js/simulation_effects_system.js " : " TarinaiEffectsSimulationSystem " ,
" js/simulation_creature_system.js " : " TarinaiCreatureSimulationSystem " ,
" js/simulation_maintenance_system.js " : " TarinaiMaintenanceSimulationSystem " ,
" js/simulation_ambient_system.js " : " TarinaiAmbientSimulationSystem " ,
}
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 " )
ok ( " named simulation system order present " )
def simulation_boundary_smoke ( ) - > None :
sources = { rel : read_text ( ROOT / rel ) for rel in [
" js/simulation_runtime_helpers.js " ,
" js/simulation_environment_system.js " ,
" js/simulation_item_ant_system.js " ,
" js/simulation_effects_system.js " ,
" js/simulation_creature_system.js " ,
" 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 = f """
const vm = require ( ' vm ' ) ;
const context = { { console , globalThis : null } } ;
context . globalThis = context ;
context . window = context ;
vm . createContext ( context ) ;
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 ' ) ;
"""
with tempfile . NamedTemporaryFile ( " w " , encoding = " utf-8 " , suffix = " .js " , delete = False ) as f :
f . write ( js )
tmp = Path ( f . name )
try :
result = subprocess . run ( [ " node " , str ( tmp ) ] , cwd = ROOT , text = True , capture_output = True )
finally :
tmp . unlink ( missing_ok = True )
if result . returncode != 0 :
sys . stderr . write ( result . stdout )
sys . stderr . write ( result . stderr )
fail ( " simulation boundary smoke test failed " )
ok ( " simulation boundary smoke test passed " )
def check_update_policy_boundaries ( ) - > None :
data = manifest ( )
js_files = data [ " js " ]
required = [ " js/item_update_policy.js " , " js/tarinai_update_policy.js " ]
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 " ) ) :
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 " )
item_policy = read_text ( ROOT / " js/item_update_policy.js " )
tarinai_policy = read_text ( ROOT / " js/tarinai_update_policy.js " )
scheduler = read_text ( ROOT / " js/item_update_scheduler.js " )
sim_helpers = read_text ( ROOT / " js/simulation_runtime_helpers.js " )
creature_system = read_text ( ROOT / " js/simulation_creature_system.js " )
required_tokens = [
( item_policy , " global.TarinaiItemUpdatePolicy " ) ,
( item_policy , " itemUpdateInterval " ) ,
( item_policy , " forScheduledItems " ) ,
( tarinai_policy , " global.TarinaiCreatureUpdatePolicy " ) ,
( tarinai_policy , " updateInterval " ) ,
( scheduler , " TarinaiItemUpdatePolicy " ) ,
( sim_helpers , " TarinaiItemUpdatePolicy " ) ,
( creature_system , " TarinaiCreatureUpdatePolicy " ) ,
]
for source , token in required_tokens :
if token not in source :
fail ( f " update policy boundary token missing: { token } " )
duplicated_item_policy_sources = [ scheduler , sim_helpers ]
for source in duplicated_item_policy_sources :
if " const REALTIME_ITEM_TYPES " in source or " const PIN_ITEM_TYPES " in source or " SCHEDULED_ITEM_TYPES = " in source :
fail ( " item update policy constants are duplicated outside item_update_policy.js " )
if " function tarinaiUpdateInterval " in creature_system :
fail ( " Tarinai update cadence still lives inside simulation_creature_system.js " )
js = f """
const vm = require ( ' vm ' ) ;
const fs = require ( ' fs ' ) ;
const context = { { console , globalThis : null } } ;
context . globalThis = context ;
context . window = context ;
context . isServingFoodType = ( type ) = > type == = ' food ' ;
context . TarinaiItemRuntime = { {
updateOne ( item , dt ) { { item . runs = ( item . runs | | 0 ) + 1 ; item . lastDt = dt ; return true ; } }
} } ;
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 } } ) ;
} }
const world = { { time : 0 , items : [ { { type : ' ball ' } } , { { type : ' food ' } } , { { type : ' grass ' } } ] , ensureItemBuckets ( ) { { } } , itemBucketRebuildsTotal : 0 } } ;
if ( context . TarinaiItemUpdateScheduler . run ( world , 0.1 ) < 1 ) throw new Error ( ' realtime item did not run ' ) ;
world . time = 5.1 ;
if ( context . TarinaiItemUpdateScheduler . run ( world , 0.1 ) < 1 ) throw new Error ( ' scheduled item did not run ' ) ;
"""
with tempfile . NamedTemporaryFile ( " w " , encoding = " utf-8 " , suffix = " .js " , delete = False ) as f :
f . write ( js )
tmp = Path ( f . name )
try :
result = subprocess . run ( [ " node " , str ( tmp ) ] , cwd = ROOT , text = True , capture_output = True )
finally :
tmp . unlink ( missing_ok = True )
if result . returncode != 0 :
sys . stderr . write ( result . stdout )
sys . stderr . write ( result . stderr )
fail ( " update policy scheduler smoke test failed " )
ok ( " update policy boundaries present " )
def check_entity_runtime_boundary ( ) - > None :
data = manifest ( )
js_files = data [ " js " ]
required = [ " js/item_runtime.js " , " js/tarinai_runtime.js " ]
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 " ) ) :
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 " )
item_runtime = read_text ( ROOT / " js/item_runtime.js " )
tarinai_runtime = read_text ( ROOT / " js/tarinai_runtime.js " )
item_scheduler = read_text ( ROOT / " js/item_update_scheduler.js " )
sim_helpers = read_text ( ROOT / " js/simulation_runtime_helpers.js " )
creature_system = read_text ( ROOT / " js/simulation_creature_system.js " )
required_tokens = [
( item_runtime , " global.TarinaiItemRuntime " ) ,
( 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 " ) ,
]
for source , token in required_tokens :
if token not in source :
fail ( f " entity runtime boundary token missing: { token } " )
ok ( " entity update runtime boundary present " )
def check_tarinai_update_pipeline_boundary ( ) - > None :
data = manifest ( )
js_files = data [ " js " ]
required = [
" 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 " ,
" js/tarinai_contact_item_system.js " ,
" js/tarinai_item_interaction_system.js " ,
" js/tarinai_update_step_frame.js " ,
" js/tarinai_update_step_ai.js " ,
" js/tarinai_update_step_environment.js " ,
" js/tarinai_update_step_movement.js " ,
" js/tarinai_update_step_health.js " ,
" js/tarinai_update_pipeline.js " ,
]
for rel in required :
if rel not in js_files :
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 " ,
" js/tarinai_contact_item_system.js " ,
" js/tarinai_item_interaction_system.js " ,
" js/tarinai_sunbath_system.js " ,
" js/tarinai_cursor_care_system.js " ,
" js/tarinai_local_environment_system.js " ,
" js/tarinai_social_move_life.js " ,
" js/tarinai_update_step_frame.js " ,
" js/tarinai_update_step_ai.js " ,
" js/tarinai_update_step_environment.js " ,
" js/tarinai_update_step_movement.js " ,
" js/tarinai_update_step_health.js " ,
" js/tarinai_update_pipeline.js " ,
" js/tarinai_runtime.js " ,
]
if [ rel for rel in js_files if rel in expected_order ] != expected_order :
fail ( " Tarinai update pipeline load order is invalid " )
exports = {
" 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 " ,
" js/tarinai_contact_item_system.js " : " global.TarinaiContactItemSystem " ,
" js/tarinai_item_interaction_system.js " : " global.TarinaiItemInteractionSystem " ,
" js/tarinai_update_step_frame.js " : " global.TarinaiFrameUpdateStep " ,
" js/tarinai_update_step_ai.js " : " global.TarinaiAiUpdateStep " ,
" js/tarinai_update_step_environment.js " : " global.TarinaiEnvironmentUpdateStep " ,
" js/tarinai_update_step_movement.js " : " global.TarinaiMovementUpdateStep " ,
" js/tarinai_update_step_health.js " : " global.TarinaiHealthUpdateStep " ,
" js/tarinai_update_pipeline.js " : " global.TarinaiUpdatePipeline " ,
}
for rel , token in exports . items ( ) :
if token not in read_text ( ROOT / rel ) :
fail ( f " Tarinai update pipeline export missing: { rel } " )
runtime = read_text ( ROOT / " js/tarinai_runtime.js " )
2026-06-26 19:04:32 +09:00
if " TarinaiUpdatePipeline?.updateOne " not in runtime and " TarinaiUpdatePipeline.updateOne " not in runtime :
fail ( " Tarinai runtime does not route through TarinaiUpdatePipeline " )
2026-06-26 12:46:32 +09:00
needs_items = read_text ( ROOT / " js/tarinai_needs_items.js " )
sunbath_system = read_text ( ROOT / " js/tarinai_sunbath_system.js " )
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 " )
2026-06-26 19:04:32 +09:00
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 " )
2026-06-26 12:46:32 +09:00
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 " ] :
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 " )
food_interaction = read_text ( ROOT / " js/tarinai_food_interaction_system.js " )
contact_interaction = read_text ( ROOT / " js/tarinai_contact_item_system.js " )
for source , token in [
( sunbath_system , " this.sunbathFrameTimer " ) ,
( cursor_system , " this.nextCursorHeartAt " ) ,
( local_env_system , " grassComfort " ) ,
( interaction_context , " const foodNeed = Number(tarinai.needRaw?.food " ) ,
( food_interaction , " tarinai.applyParamItemEffect " ) ,
( contact_interaction , " tarinai.applyWaterEffect(dt, it.type) " ) ,
] :
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 :
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 " )
2026-06-26 19:04:32 +09:00
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 " )
2026-06-26 12:46:32 +09:00
if " TarinaiItemInteractionSystem.updateOne " not in read_text ( ROOT / " js/tarinai_update_step_ai.js " ) :
fail ( " Tarinai AI step does not delegate item interactions to subsystem " )
if " TarinaiCursorCareSystem.updateOne " not in read_text ( ROOT / " js/tarinai_update_step_frame.js " ) :
fail ( " Tarinai frame step does not delegate cursor care to subsystem " )
if " TarinaiLocalEnvironmentSystem.updateOne " not in read_text ( ROOT / " js/tarinai_update_step_environment.js " ) :
fail ( " Tarinai environment step does not delegate local environment to subsystem " )
if " TarinaiSunbathSystem.updateOne " not in read_text ( ROOT / " js/tarinai_update_step_movement.js " ) :
fail ( " Tarinai movement step does not delegate sunbath to subsystem " )
pipeline = read_text ( ROOT / " js/tarinai_update_pipeline.js " )
2026-06-26 19:04:32 +09:00
for token in [ " TarinaiFrameUpdateStep " , " TarinaiAiUpdateStep " , " TarinaiEnvironmentUpdateStep " , " TarinaiMovementUpdateStep " , " TarinaiHealthUpdateStep " ] :
2026-06-26 12:46:32 +09:00
if token not in pipeline :
fail ( f " Tarinai update pipeline missing step token: { token } " )
js = f """
const vm = require ( ' vm ' ) ;
const fs = require ( ' fs ' ) ;
const context = { { console , globalThis : null } } ;
context . globalThis = context ;
context . window = context ;
context . CONFIG = { { hungerPerMinute : 60 , lonelinessPerMinute : 60 , energyPerMinute : 60 } } ;
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 . applyNeedShock = ( ) = > undefined ;
context . applyNeedRelief = ( ) = > undefined ;
context . calculateStressFromNeeds = ( ) = > 0 ;
context . applyGroundStressModifier = ( t , v ) = > v ;
context . createDefaultNeeds = ( ) = > ( { { food : 0 , sleep : 0 , health : 0 , safety : 0 , social : 0 , fulfill : 0 } } ) ;
context . tarinaiMaxEnergy = ( ) = > 100 ;
context . tarinaiMetabolicHungerScale = ( ) = > 1 ;
vm . createContext ( context ) ;
for ( const file of { required ! r } ) { {
vm . runInContext ( fs . readFileSync ( file , ' utf8 ' ) , context , { { filename : file } } ) ;
} }
const calls = [ ] ;
const world = { {
time : 1 ,
groundType : ' soil ' ,
weather : ' cloudy ' ,
colonyMood : { { id : ' relaxed ' } } ,
selected : null ,
spendScheduledWork ( ) { { return true ; } } ,
temperatureAt ( ) { { return 0.62 ; } } ,
lightLevel ( ) { { return 0.5 ; } } ,
spawnBleedEffect ( ) { { calls . push ( ' bleed ' ) ; } } ,
resolveFenceCollision ( ) { { calls . push ( ' fence ' ) ; } } ,
} } ;
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 ,
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 ,
sunbathCooldown : 0 , fightTimer : 0 , fightCooldown : 0 , defeatedTimer : 0 , stretchTimer : 0 ,
grassEatTimer : 0 , hurtTimer : 0 , fallTimer : 0 , tempTimer : 0 , tempComfort : 0.62 ,
hunger : 10 , loneliness : 0 , energy : 100 , trait : { { hunger : 1 , social : 1 , sleep : 1 } } ,
state : ' idle ' , aiTimer : 0 , envTimer : 0 , needs : context . createDefaultNeeds ( ) , needRaw : context . createDefaultNeeds ( ) ,
updateZunchiDisease ( ) { { calls . push ( ' zunchiDisease ' ) ; } } ,
updateSpecialDiseases ( ) { { calls . push ( ' specialDisease ' ) ; } } ,
freezeSleepDiseaseMotion ( ) { { return false ; } } ,
applyCursorContactCare ( ) { { calls . push ( ' cursorCare ' ) ; } } ,
personalityProfile ( ) { { return { { fear : 1 } } ; } } ,
updateFacing ( ) { { calls . push ( ' facing ' ) ; } } ,
updateSunbath ( ) { { calls . push ( ' sunbath ' ) ; } } ,
maintainTargetProgress ( ) { { calls . push ( ' targetProgress ' ) ; } } ,
move ( ) { { calls . push ( ' move ' ) ; } } ,
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 ) ;
} }
"""
with tempfile . NamedTemporaryFile ( " w " , encoding = " utf-8 " , suffix = " .js " , delete = False ) as f :
f . write ( js )
tmp = Path ( f . name )
try :
result = subprocess . run ( [ " node " , str ( tmp ) ] , cwd = ROOT , text = True , capture_output = True )
finally :
tmp . unlink ( missing_ok = True )
if result . returncode != 0 :
sys . stderr . write ( result . stdout )
sys . stderr . write ( result . stderr )
fail ( " Tarinai update pipeline smoke test failed " )
ok ( " Tarinai update pipeline boundary present " )
def check_item_lifecycle_pipeline_boundary ( ) - > None :
data = manifest ( )
js_files = data [ " js " ]
dynamic_required = [
" js/item_dynamic_tool_system.js " ,
" js/item_dynamic_ball_system.js " ,
" js/item_dynamic_duplicator_system.js " ,
" js/item_dynamic_pin_system.js " ,
" js/item_dynamic_zunchi_system.js " ,
" js/item_dynamic_system.js " ,
]
required = [
" js/item_lifecycle_support.js " ,
* dynamic_required ,
" js/item_lifecycle_decay_system.js " ,
" js/item_lifecycle_growth_system.js " ,
" js/item_lifecycle_step_frame.js " ,
" js/item_lifecycle_step_decay.js " ,
" js/item_lifecycle_step_dynamic.js " ,
" js/item_lifecycle_step_growth.js " ,
" js/item_lifecycle_pipeline.js " ,
]
for rel in required :
if rel not in js_files :
fail ( f " Item lifecycle pipeline module missing from manifest: { rel } " )
expected_order = [
" js/item_lifecycle_support.js " ,
" js/item_lifecycle_runtime.js " ,
" js/item_update_policy.js " ,
* dynamic_required ,
" js/item_lifecycle_decay_system.js " ,
" js/item_lifecycle_growth_system.js " ,
" js/item_lifecycle_step_frame.js " ,
" js/item_lifecycle_step_decay.js " ,
" js/item_lifecycle_step_dynamic.js " ,
" js/item_lifecycle_step_growth.js " ,
" js/item_lifecycle_pipeline.js " ,
" js/item_runtime.js " ,
]
if [ rel for rel in js_files if rel in expected_order ] != expected_order :
fail ( " Item lifecycle pipeline load order is invalid " )
exports = {
" js/item_lifecycle_support.js " : " global.TarinaiItemLifecycleRuntimeSupport " ,
" js/item_dynamic_tool_system.js " : " global.TarinaiItemDynamicToolSystem " ,
" js/item_dynamic_ball_system.js " : " global.TarinaiItemDynamicBallSystem " ,
" js/item_dynamic_duplicator_system.js " : " global.TarinaiItemDynamicDuplicatorSystem " ,
" js/item_dynamic_pin_system.js " : " global.TarinaiItemDynamicPinSystem " ,
" js/item_dynamic_zunchi_system.js " : " global.TarinaiItemDynamicZunchiSystem " ,
" js/item_dynamic_system.js " : " global.TarinaiItemDynamicSystem " ,
" js/item_lifecycle_decay_system.js " : " global.TarinaiItemDecaySystem " ,
" js/item_lifecycle_growth_system.js " : " global.TarinaiItemGrowthSystem " ,
" js/item_lifecycle_step_frame.js " : " global.TarinaiItemFrameLifecycleStep " ,
" js/item_lifecycle_step_decay.js " : " global.TarinaiItemDecayLifecycleStep " ,
" js/item_lifecycle_step_dynamic.js " : " global.TarinaiItemDynamicLifecycleStep " ,
" js/item_lifecycle_step_growth.js " : " global.TarinaiItemGrowthLifecycleStep " ,
" js/item_lifecycle_pipeline.js " : " global.TarinaiItemLifecyclePipeline " ,
}
for rel , token in exports . items ( ) :
if token not in read_text ( ROOT / rel ) :
fail ( f " Item lifecycle pipeline export missing: { rel } " )
runtime = read_text ( ROOT / " js/item_runtime.js " )
2026-06-26 19:04:32 +09:00
if " TarinaiItemLifecyclePipeline?.updateOne " not in runtime and " TarinaiItemLifecyclePipeline.updateOne " not in runtime :
fail ( " item runtime does not route through TarinaiItemLifecyclePipeline " )
2026-06-26 12:46:32 +09:00
pipeline = read_text ( ROOT / " js/item_lifecycle_pipeline.js " )
2026-06-26 19:04:32 +09:00
for token in [ " TarinaiItemFrameLifecycleStep " , " TarinaiItemDecayLifecycleStep " , " TarinaiItemDynamicLifecycleStep " , " TarinaiItemGrowthLifecycleStep " ] :
2026-06-26 12:46:32 +09:00
if token not in pipeline :
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 :
fail ( " Item dynamic lifecycle step does not delegate to TarinaiItemDynamicSystem " )
for token in [ " TarinaiItemDynamicToolSystem " , " TarinaiItemDynamicBallSystem " , " TarinaiItemDynamicDuplicatorSystem " , " TarinaiItemDynamicPinSystem " , " TarinaiItemDynamicZunchiSystem " ] :
if token not in dynamic_system :
fail ( f " Item dynamic system missing subsystem token: { token } " )
for rel in [ " js/item_dynamic_tool_system.js " , " js/item_dynamic_ball_system.js " , " js/item_dynamic_duplicator_system.js " , " js/item_dynamic_pin_system.js " , " js/item_dynamic_zunchi_system.js " ] :
body = read_text ( ROOT / rel )
2026-06-26 19:04:32 +09:00
for token in [ " item.updateFan " , " item.updateRotator " , " item.updateBall " , " item.updateDuplicator " , " item.updatePushpin " , " item.updateZunchiMotion " , " item.updateZunchi " ] :
2026-06-26 12:46:32 +09:00
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 :
fail ( " item lifecycle support export missing " )
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 " )
2026-06-26 19:04:32 +09:00
for helper_token in [ " function duplicatorLoadTypeForItem " , " function duplicatorApplyStoredPin " ] :
2026-06-26 12:46:32 +09:00
if helper_token in lifecycle_runtime :
fail ( f " support helper still lives in item_lifecycle_runtime.js: { helper_token } " )
2026-06-26 19:04:32 +09:00
for token in [ " updateBall(dt, worldRef) { return window.TarinaiItemDynamicBallSystem " , " attachPushpin(t, worldRef " ] :
2026-06-26 12:46:32 +09:00
if token not in lifecycle_runtime :
2026-06-26 19:04:32 +09:00
fail ( f " prototype subsystem facade missing: { token } " )
2026-06-26 12:46:32 +09:00
js = f """
const vm = require ( ' vm ' ) ;
const fs = require ( ' fs ' ) ;
const context = { { console , globalThis : null } } ;
context . globalThis = context ;
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 . isPinType = ( type ) = > type == = ' pushpin ' ;
context . normalizeGrassStage = ( item ) = > { { item . normalized = true ; } } ;
context . stableUnit = ( ) = > 0.5 ;
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 ' ) ;
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 ) ;
const dynamic = { {
type : ' ball ' , dead : false , age : 0 , x : 80 , y : 80 , prevX : 80 , prevY : 80 ,
vx : 12 , vy : 0 , r : 18 , spin : 0 , spinVelocity : 0
} } ;
const ballWorld = { { . . . world , w : 500 , h : 500 , nearbyItems ( ) { { return [ ] ; } } } } ;
context . TarinaiItemLifecyclePipeline . updateOne ( dynamic , 0.25 , ballWorld , { { legacyUpdate ( ) { { throw new Error ( ' legacy should not run ' ) ; } } } } ) ;
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 ' ) ;
"""
with tempfile . NamedTemporaryFile ( " w " , encoding = " utf-8 " , suffix = " .js " , delete = False ) as f :
f . write ( js )
tmp = Path ( f . name )
try :
result = subprocess . run ( [ " node " , str ( tmp ) ] , cwd = ROOT , text = True , capture_output = True )
finally :
tmp . unlink ( missing_ok = True )
if result . returncode != 0 :
sys . stderr . write ( result . stdout )
sys . stderr . write ( result . stderr )
fail ( " Item lifecycle pipeline smoke test failed " )
ok ( " Item lifecycle pipeline boundary present " )
2026-06-24 13:48:22 +09:00
def main ( ) - > None :
data = manifest ( )
check_files_exist ( data )
2026-06-25 14:51:58 +09:00
check_unified_item_registry ( )
check_docs_removed ( )
2026-06-24 13:48:22 +09:00
check_versions ( data )
check_index_order ( data )
check_service_worker_order ( data )
node_check ( [ * ( ROOT / " js " ) . glob ( " *.js " ) , ROOT / " service-worker.js " ] )
action_spec_smoke ( )
behavior_state_smoke ( )
2026-06-25 14:51:58 +09:00
check_basic_action_spec_runtime ( )
check_social_action_spec_runtime ( )
check_action_spec_no_legacy_hooks ( )
2026-06-24 13:48:22 +09:00
check_runtime_diagnostics_hooks ( )
check_social_balance_guards ( )
check_item_bucket_polish ( )
2026-06-25 14:51:58 +09:00
check_current_save_format ( )
check_phase_and_input_split ( )
2026-06-26 12:46:32 +09:00
check_system_order_boundary ( )
simulation_boundary_smoke ( )
check_entity_runtime_boundary ( )
check_item_lifecycle_pipeline_boundary ( )
check_tarinai_update_pipeline_boundary ( )
check_update_policy_boundaries ( )
2026-06-25 14:51:58 +09:00
check_processing_split ( )
2026-06-26 12:46:32 +09:00
command_dispatcher_smoke ( )
2026-06-24 13:48:22 +09:00
check_legacy_behavior_refs ( )
if __name__ == " __main__ " :
main ( )