oriverse-engine 0.1.7 → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,21 +1,24 @@
1
1
 
2
2
  >>>>>>>>>>>>
3
3
  Weave World Description Language Documentation
4
- Version:21Aug26-00:56-09502
4
+ Version:31Aug26-00:34-35561
5
5
  >>>>>>>>>>>>
6
6
 
7
7
  <weave_language>
8
8
  - Weave is used to code on Oriverse (syntax similar to Swift for logic, plus scene declarations at top level).
9
9
  - A Weave file has two kinds of top-level content:
10
- - scene declarations: `object`, `saved`, `physics`, `crowd`, `display`, `region`, and lights `pointLight`/`spotLight`/`areaLight` — describe what exists in the world.
10
+ - scene declarations: `object`, `saved`, `physics`, `crowd`, `walker`, `display`, `region`, and lights `pointLight`/`spotLight`/`areaLight` — describe what exists in the world.
11
11
  - class code: `#class <Name> ... #end` blocks of logic; each block compiles to bytecode attached to a game object.
12
- - Compiler directives (top-level only): #class, #shader (custom WGSL on the engine lit material — lighting/shadows/IBL stay engine-owned; `@vertex { }` mutates ctx.world_pos for wind/sway, fragment writes out.base_color/world_normal/orm; bind via `material M { shader = "S" }`; live knobs via SetMaterialParameterNumber; body authoring in the shader_dsl skill), #texture (CPU-baked procedural PBR texture; auto-creates `material <Name>` usable as `material = "<Name>"`; defaults to triplanar/repeat/opaque, supports `mapping = uv;`, `wrap = clamp;`, `render_mode = cutout;`, and scalar `out.alpha`; see <texture_dsl_usage>; body authoring in the texture_dsl skill), #mesh (local CPU-baked procedural static mesh model `mesh.<Name>`; see <mesh_dsl_usage>; body authoring in the mesh_dsl skill), #tree (procedural tree species `tree.<Name>`; see <tree_dsl_usage>), #forest (scattered-tree region `forest.<Name>`; see <forest_dsl_usage>), #scatter (scattered-model region `scatter.<Name>`, display-only, no collision; see <scatter_dsl_usage>), #particles (local particle effect JSON asset `effect.<Name>`; `particleTextureName` takes `"flipbook.<word>"` for a shared-library animated atlas, `"texture.<Texture>.color"` for a local #texture, or `"image.<User>.<Name>"` for a known ori image asset; see particle_vfx skill), #sound (local CPU-baked procedural sound asset `sound.<name>`; see <sound_dsl_usage>), #adjuster (user-tunable look knobs; see <adjuster_usage>), #rules (see <world_rules>), #end, #set <Name> = <Number> (TileSize, Realistic; texture policy, display-only: `#set TextureMaxResolution 1024` caps every market texture this world downloads to <=1024px [picks the market's smaller tier], `#set TextureFullResolutionAssets "model.a.x, model.b.y"` always loads listed assets at full resolution, `#set TextureFullResolutionUpgrade 1` enables the desktop post-load full-res texture re-fetch [default off: the world keeps its load-time texture tier — project-load assets land quarter-res; wasm/mobile never run the upgrade]), #doc_version <N> (authoring doc version; see <updating_old_worlds>).
12
+ - Compiler directives (top-level only): #class, #shader (custom WGSL on the engine lit material — lighting/shadows/IBL stay engine-owned; `@vertex { }` mutates ctx.world_pos for wind/sway, fragment writes out.base_color/world_normal/orm; bind via `material M { shader = "S" }`; live knobs via SetMaterialParameterNumber; body authoring in the shader_dsl skill), #texture (CPU-baked procedural PBR texture; auto-creates `material <Name>` usable as `material = "<Name>"`; defaults to triplanar/repeat/opaque (triplanar follows the instance - rotated/moved/duplicated props keep their pattern; `mapping = triplanar_world;` = world-space projection that continues across objects for modular kits, `mapping = uv;` = authored UVs), supports `wrap = clamp;`, `filter = nearest;` (pixel-art), `render_mode = cutout;`, and scalar `out.alpha`; see <texture_dsl_usage>; body authoring in the texture_dsl skill), #mesh (local CPU-baked procedural static mesh model `mesh.<Name>`; see <mesh_dsl_usage>; body authoring in the mesh_dsl skill), #tree (procedural tree species `tree.<Name>`; see <tree_dsl_usage>), #forest (scattered-tree region `forest.<Name>`; see <forest_dsl_usage>), #scatter (scattered-model region `scatter.<Name>`, display-only, no collision; see <scatter_dsl_usage>), #road_network (road/street graph meshed at load into junction-merged tiles + walkable slabs; grammar in its bullet below), #placement (grid-repeated model family - facade/street rhythm; see <placement_usage>), #particles (local particle effect JSON asset `effect.<Name>`; `particleTextureName` takes `"flipbook.<word>"` for a shared-library animated atlas, `"texture.<Texture>.color"` for a local #texture, or `"image.<User>.<Name>"` for a known ori image asset; see particle_vfx skill), #sound (local CPU-baked procedural sound asset `sound.<name>`; see <sound_dsl_usage>), #adjuster (user-tunable look knobs; see <adjuster_usage>), #rules (see <world_rules>), #end, #set <Name> = <Number> (TileSize, Realistic; texture policy, display-only: `#set TextureMaxResolution 1024` caps every market texture this world downloads to <=1024px [picks the market's smaller tier], `#set TextureFullResolutionAssets "model.a.x, model.b.y"` always loads listed assets at full resolution, `#set TextureFullResolutionUpgrade 1` enables the desktop post-load full-res texture re-fetch [default off: the world keeps its load-time texture tier — project-load assets land quarter-res; wasm/mobile never run the upgrade]), #doc_version <N> (authoring doc version; see <updating_old_worlds>).
13
13
  - Scene numeric fields support constant arithmetic: `+`, `-`, `*`, `/`, unary sign, parentheses.
14
14
  - One statement per line; separate statements on the same line with `;`. An empty list literal `[]` needs a List-typed context, e.g. `var H: List<Number> = []`.
15
15
  - Numbers are deterministic 48.16 fixed-point; tiny fractions lose precision — track small quantities in a smaller unit (e.g. hundredths of a cm as integers).
16
16
  - No recursion: direct or mutual func call cycles are a compile error; rewrite as loops (a List can serve as an explicit stack).
17
+ - Class-body functions sit beside events: `func Heal(Target: Object, Amount: Number) -> Number { return Amount }`; omit `-> Type` when nothing is returned; zero-arg funcs still declare `()`; another instance's func needs a typed cast (`(u as Trooper).TakeHit(10)`).
18
+ - Self is the current object instance; calling an Object function without a caller implies Self.
17
19
  - Scene object declarations can set position, rotation, size, height, model; model is an Oriverse URL like `ori.username.modelname`; attach a class with `:` (e.g. `object Foo : MyClass { ... }`).
18
- - Default mesh-backed `object`/`physics`/`crowd`/`display`: `position=(x,y,0)` means bottom-center pivot on ground Z=0. Effects/lights/regions/terrain/groups/canvases use center placement.
20
+ - Default mesh-backed `object`/`physics`/`crowd`/`walker`/`display`: `position=(x,y,0)` means bottom-center pivot on ground Z=0. Effects/lights/regions/terrain/groups/canvases use center placement.
21
+ - Population body types, split by what steers them: `walker` = a `display`-based path-walker (no physics body, never in the avoidance solver, ~zero per-citizen cost) — citizens, villagers, animals; its `StartTilePathFollow` waypoints ground-conform to terrain (plain `display` keeps its z). `crowd` = an ORCA avoidance agent (physics body, raycast/touch surface, separation + wall dodging, always pays the solver) — units avoidance must steer: combat, RTS squads. City dwellers are `walker`; soldiers are `crowd`.
19
22
  - Dynamic lights: `pointLight Lamp { position=(x,y,z) radius=600 intensity=5 color=#ffd9a0 }`; `spotLight` shines along local +X (aim with rotation), `areaLight` emits from its `size` rectangle. `radius` = falloff range in cm; `size` does NOT change a point/spot light (editor gizmo only); `cast_shadow = true` on point/spot only. Nest in `saved` templates to spawn lights from code; for lamps built into a model prefer the mesh `emitter(...)` socket.
20
23
  - For axis-aligned cube walls/floors/platforms, prefer `bounds_min=(x1,y1,z1)` + `bounds_max=(x2,y2,z2)` over `position`+`size`; it names exact world-space box corners and avoids center/half-size mistakes. Example: `object NorthWall { model="cube" bounds_min=(-1500,1100,0) bounds_max=(1500,1180,280) }`. Cube boxes only (`model="cube"`; `region_cube` for regions) — other models must use position+size. Do not combine with position/rotation/size/height/tile fields.
21
24
  - Object size clamps per axis at load: 500m static-collidable, 100m movable-physics, 1km with collision off — tile bigger floors/walls from 500m pieces.
@@ -31,6 +34,12 @@ Weave World Description Language Documentation
31
34
  - When the user states a lasting preference ("from now on always..."), persist it as a line in `#rules` (create the block if missing).
32
35
  </world_rules>
33
36
 
37
+ <game_design>
38
+ - Every player action and game event gets an immediate VISIBLE read; a static response reads as broken. Wire the read while adding the mechanic: UI press/select -> tween the element (scale or color flash; see <easing>) and show the state it caused (training/cooldown/build progress as a fill or hover bar); hit -> SpawnDamageNumber or a flash on the target; kill/destroy -> ShatterAndDestroy or a #particles burst; explosion -> effect + ExplosionShake; pickup/XP/heal -> SpawnFloatingText; wave/level/win/lose -> banner text. A matching `alias sound.<word>` on top is optional garnish.
39
+ - A DENIED action gets the same read: state why at the click (short toast/flash + a denial sound) and dim/disable controls whose prerequisite is missing - a silent no-op reads as a bug.
40
+ - A playable level is structure, not a floor + a wall: bounds, cover at engagement range, at least one height change (ramp/platform/tower), a landmark or two for orientation, lanes that loop rather than dead-end. Bare-colored `cube` floors and walls read as a prototype - large surfaces get a `#texture`/material or terrain. Which tool builds which piece: <mesh_dsl_usage>.
41
+ </game_design>
42
+
34
43
  <asset_aliases>
35
44
  - Aliases make the top of the world file a small asset dictionary: swap a model/effect/image/sound everywhere by editing one line. They also expose assets for runtime manipulation and let a human rebind them from the Build panel [Replace] button.
36
45
  - Name aliases by TYPE: `alias.<type>.<name>` with <type> in {model, effect, image, sound}, e.g. `model.goblin`, `effect.explosion`, `image.gold_icon`, `sound.hit`. A typed alias must resolve to a matching asset type.
@@ -49,27 +58,28 @@ Weave World Description Language Documentation
49
58
 
50
59
  <asset_word_library>
51
60
  - Established library words per alias namespace, plus `flipbook.<word>` particle textures. PREFER an existing word when one matches the need — it binds instantly with no generation wait or spend. Other words still work: misses show a placeholder and auto-generate in the background (flipbook words are minted deliberately via the flipbook_gen tool instead).
52
- model: abandoned_car alchemist alien_bug alien_hive alley_monster ammo_crate amphora anchor aqua_penguin assembler athlete aunt auntie axe ball banana banana_peel bandage bandit barn barracks barrel barrel_2 base_core baseball_bat battle_car bed bee beehive belt bench bench_2 berry_bush billboard blacksmith blk_probe_box boar boat boat_parts bomb bone book boomerang boulder boulder_2 box bread_slice bread_slice_2 brick bridge buddhist_temple bush bush_2 bush_a butter cabbage camp_tent camp_tent_2 campfire canvas_tent car_wheel cargo_truck carrot cart carved_owl_statue cash_bundle cat cat_blacksmith cat_cart_keeper cat_chef cat_engineer cat_factory_worker cat_merchant cat_rickshaw chair chair_2 checkpoint cheese cheese_2 chest chicken chicken_coop chime_lantern chunk_floor circular_saw clay_buddy cleric cloud clue_item clue_item_2 coin command_center container_04 cottage cousin cow crate crate_2 crate_a crop crop_plot crossbow crowbar crystal crystal_cluster crystal_shard cup customer customer_2 cutting_board cutting_board_2 cyclops dog dragon drill druid dual_blades durian elegant_woman ember_fox enemy_spawn energy_drink evidence_item exp_gem fan farm_boy farmhouse farmhouse_floor fence finish_arch fish fish_2 fishing_rod flag flag_2 flamethrower flashlight flat_rock_slab flower food_crate forager_hut forge forklift foundation fountain fox fridge fridge_2 frog frying_pan frying_pan_2 fuel_pump furniture_bookshelf furniture_table furniture_table_2 furniture_wardrobe gargoyle gate ghoul ghoul_2 gilded_hourglass glimmer_crate goblin godfather golden_beak_shaman golden_coin grandma grandpa grape grape_2 grass_rock grass_rock_cliff great_sword green_maple_tree greenhouse grocery_box ground_item guard guard_tower hammer hamster harpy hay_bale hay_bale_2 heal_pickup held_item horned_beast horse hospital_assistant house hungry_mouse hunter hunter_bow hunting_hammer hut ice_shard idle_character invoker_mage iron_node item_crate jam_jar kid killer knife knight lab_crate_a ladder ladder_a lamp lamp_2 lane lantern lantern_2 light_pole log_pile log_pile_2 lumber_camp machine_gun mage magnet market_stall mecha_chameleon medic medicine_crate mil_supply_set mine miner minotaur moss_boar mushroom nephew niece night_watchman ninja_runner npc nurse oak_tree officer oil_drum owl paddle palm_tree patient pearl_bush pebble person pickaxe pickup_orb pig pine_tree pistol plate plate_2 player_char police_car pond poop projectile pumpkin pyramid rabbit rabbit_runner ragdoll_fighter rain_mill rain_totem ranger requiem_gun resident revolver rifle road_sign robot_grunt robot_runner rock rocket rocket_launcher rocksample rope rope_bundle runic_obelisk sandbag sawmill school scifi_mil_crate sea_mine sedan sentinel_drone shark shield shipping_container shop_counter shop_floor shopkeeper shotgun shower shuriken signpost signpost_2 signpost_3 skull slime small_rock smg smoke_bomb snail sniper_rifle sofa soil_tile soldier spider sports_car squid stair star star_core statue stepmother stilt_house stone_pile stonecutter_lodge storehouse storm_hearth storm_shrine surgeon survivor sword table tank_hull tannery telephone television tent timber_bundle toaster toaster_2 tool_chest torch torch_2 tower training_dummy training_dummy_2 travel_pack tree tribal_cat_elder tribal_cat_villager trophy trunk turret uncle villager volleyball wagon wall warehouse watchtower water_barrel water_well watercan watering_can weapon_rack weapon_rack_2 weapon_shop_sign well wheat wheelbarrow white_tribal_cat_player wizard_staff wood_fence wood_fence_2 wooden_stool worker_robot zebrite_totem zombie
53
- image: airtank_icon ak_rifle_icon alien_bug angled_player_banner axe_icon banana_weapon_icon bandage_icon baseball_bat_icon bean_icon bean_seeds_icon berries_icon book_icon boss_skull bread_icon bullet_icon cabbage_icon campfire_icon canned_food_icon carrot_icon cheese_icon clock_icon coin_icon crop_icon crossed_swords crossed_swords_2 crosshair crowbar_icon cute_paw_cursor dash_icon deck_icon deck_icon_2 energy_icon fish_icon fishing_rod_icon flamethrower_icon flint_icon fluffy_sheep_party_portrait forest_ancient_tree_spirit_wide_splash forest_ent gold_coin_icon gold_icon gold_icon_2 grenade_launcher_icon hand_icon health_icon health_icon_2 hoe_icon horse_party_portrait hu_armor hu_headshot hu_hit hu_hitcrit hu_mortar hu_recon hu_reload hu_rifle hu_sentry hud_armor_plate_shield hud_assault_rifle_side hud_crit_hitmarker_x_red hud_headshot_skull hud_hitmarker_x_ticks hud_mortar_strike_shell hud_reload_circular_arrow hud_sentry_turret hud_uav_recon_drone hud2_drone_wing hud2_reload_arrow hud2_rifle_flat hud2_shell_burst hud2_turret_gun hud2_x_marker hud2_x_marker_red hud3_drone_stencil hud3_rifle_stencil hud3_shell_stencil hud3_skull_stencil hunger_icon inverse_trapezoid_top_bar iron_ore_icon knife_icon knight_helmet laser_gun_icon loading_splash log_icon machine_gun_icon mackerel_icon monster_head moon_icon orange_cat_party_portrait panel_frame parsnip_icon parsnip_seeds_icon pickaxe_icon pill_bottle_icon pistol_icon poison_icon potato_icon potato_seeds_icon purple_hive_mind_insect_warrior_wide_splash qa_probe_gem radish_icon revolver_icon rifle_icon road_tiles_icon road_tiles_icon_2 robot_face robot_vending_mech_champion_wide_splash rocket_launcher_icon round_table_knight_wide_splash roundabout_icon roundabout_icon_2 roundabout_ring_icon_transparent_no_background ruby_gem_icon salvage_icon sanity_icon sardine_icon scythe_icon seed_bag_icon seed_icon shotgun_icon skill_dagger_icon skill_fireaura_icon skill_hammer_icon skill_shield_icon skill_stealth_icon snapper_icon sniper_rifle_icon snowflake_spin soda_can_icon squid_icon steak_icon stone_icon straight_road_icon_transparent_no_background submachine_gun_icon sun_icon syringe_gun_icon tft_alien_portrait_black tft_boss_skull_black tft_clock_black tft_crossed_swords_black tft_forest_portrait_black tft_monster_head_black tft_robot_portrait_black tft_yokai_portrait_black torch_icon traffic_light_icon traffic_light_icon_2 traffic_light_icon_transparent_no_background tuna_icon twigs_icon undead_death_knight_wide_splash undead_skull water_bottle_icon water_drop_icon watering_can_icon white_chicken_party_portrait wood_icon yokai_mask yokai_samurai_oni_wide_splash
54
- sound: ambient_horror apartment_ambience arena_warning baby_raptor_step banana_boomerang baton_hit baton_swing bear_shotgun bite blast body_thump body_thump_2 boost boost_burst bow_hit branch_snap brass_gong cannon_blast cannon_fire car_horn card_draw card_play cash_pickup cash_pickup_2 character_select checkpoint chomp coin_pickup coin_pickup_2 corn_burst correct_chime countdown countdown_beep countdown_tick crab_blaster crab_hit crab_skill crash crash_burst critical_impact critical_impact_2 croc_machinegun crow_call crowd_cheer crystal_pickup dash_burst dodge dodge_dust dodge_dust_2 dog_bark dog_pistol door_close door_creak door_open door_open_2 door_unlock dual_hit earthquake_rumble earthquake_rumble_2 electric_burst electric_shock eliminated eliminated_2 energy_empty engine_acceleration engine_idle exit_unlock explosion finish finish_chime fish_caught fishing_cast fishing_reel flamethrower_burst food_complete food_complete_2 front_weapon_activation game_check game_end game_start gameplay_music gameplay_music_2 glimmer_bell gold_pickup golf_cup golf_swing grenade_launcher gunshot hammer_hit hammer_hit_2 heartbeat hit hook_impact hook_impact_2 hook_throw hook_throw_2 item_drop item_pickup item_pickup_2 laser_shot laser_shot_2 lasgun_shot level_up level_up_2 level_up_3 level_up_4 level_up_5 line_clear machine_gun machine_gun_reload machine_gun_shot magic_blast medkit_pickup medkit_use menu_music metal_hit metal_hit_2 mine missile_launch mole_sniper monster_attack monster_attack_2 monster_roar monster_roar_2 owl_hoot pickup piece_capture piece_lock piece_move pistol_reload pistol_shot place player_hit player_hurt player_hurt_2 police_siren punch rain_ambience raptor_step ready_up rear_ability_activation reload repair resource_pickup respawn revive rifle_reload rifle_shot rifle_shot_2 ring_out ringout robot_beam robot_blast robot_charge robot_laser rock_riff rocket_shot rot_loop round_start royal_capture score_boost shield shield_block ship_destroyed shotgun_shot signal_ping siren slab_turn slap slap_hit speed_burst stone_rotate survivor_step sword_clang sword_clang_2 sword_hit sword_hit_2 tire_skid toast_pop toy_pop trex_step turn_alert ui_click ui_click_2 ui_click_3 ui_click_4 ui_click_5 vehicle_damaged vehicle_destroyed victory victory_fanfare victory_fanfare_2 victory_sting victory_sting_2 vortex_hum wall_deploy wall_pickup warning_siren water_splash wave_crash weapon_switch whistle wind_gust wind_gust_2 wolf_howl wood_impact wrong_buzz zebrite_chime zombie_attack zombie_growl zombie_roar
55
- effect: acid_splash arrow_hit bite_splash blood_hit blood_splash boost_burst cannon_blast capture_burst cartoon_impact cash_sparkle cash_sparkle_2 coin_sparkle collapse_dust crash_burst critical_smoke damage_smoke damage_sparks damage_sparks_2 damage_sparks_3 dash_trail dash_trail_2 dismember dodge_dust dust_puff dust_puff_2 dust_puff_3 electric_burst eliminated_burst explosion explosion_burst finish_confetti fire_burst fireball fireball_burst ghost_mist gold_rush golf_cup golf_hit grenade_blast hammer_impact heal_glow healing_glow hit_burst hook_hit hook_hit_2 impact_flash_critical impact_flash_small impact_shockwave impact_shockwave_2 impact_shockwave_3 impact_sparks ink_cloud item_glow magic_blast magic_sparkle magic_vortex metal_hit meteor_fire mine_burst muzzle_flash objective_pickup player_hit poison_cloud rage_burst rainbow_burst rainbow_burst_2 rapid_slash respawn_burst revive_burst revive_glow rickshaw_burn ringout_explosion rocket_explosion rot_pulse rune_pickup shield_burst shield_pop slap_hit slash_burst slash_burst_2 slash_burst_3 smoke_puff smoke_puff_2 sonar_pulse sparkle_burst sparkle_burst_2 sparkle_burst_3 sparkler speed_burst step_dust stun_stars stun_stars_2 stun_stars_3 sword_slash tank_burning tank_explosion terrain_blast top_collision_impact w_firethrowe w_firethrust_finale_2026 water_burst water_splash wind_leaves
61
+ model: abandoned_car alchemist alien_bug alien_hive alley_monster ambulance ammo_crate amphora anchor aqua_penguin assembler athlete aunt auntie automaton awakened axe ball bamboo banana banana_peel bandage bandit barn barracks barrel barrel_2 base_core baseball_bat battle_car bed bee beehive belt bench bench_2 berry_bush billboard blacksmith blk_probe_box boar boat boat_parts bomb bone book boomerang boulder boulder_2 box bread_slice bread_slice_2 brick bridge buddhist_temple bush bush_2 bush_a butter byrd cabbage camp_tent camp_tent_2 campfire canvas_tent car_wheel cargo_truck carrot cart carved_owl_statue cash_bundle cat cat_blacksmith cat_cart_keeper cat_chef cat_engineer cat_factory_worker cat_merchant cat_rickshaw centurion chair chair_2 champ checkpoint cheese cheese_2 chest chicken chicken_coop chime_lantern chosen chunk_floor circular_saw clay_buddy cleric cloud clue_item clue_item_2 coin collector column command_center container_04 cottage cousin cow crate crate_2 crate_a crop crop_plot crossbow crowbar crystal crystal_cluster crystal_shard cultist cup customer customer_2 cutting_board cutting_board_2 cyclops darkling dead_tree dog donu dragon drill druid dual_blades durian elegant_woman elegant_woman_2 ember_fox enemy_spawn energy_drink evidence_item exp_gem fan farm_boy farmhouse farmhouse_floor fence finish_arch fire_truck fish fish_2 fishing_boat fishing_boat_2 fishing_rod flag flag_2 flamethrower flashlight flat_rock_slab flower fog_horn food_crate forager_hut forge forklift foundation fountain fox fridge fridge_2 frog frying_pan frying_pan_2 fuel_pump fungus furniture_bookshelf furniture_table furniture_table_2 furniture_wardrobe gargoyle gate generator ghoul ghoul_2 giant_head gilded_hourglass glimmer_crate goblin godfather godzilla golden_beak_shaman golden_coin grandma grandpa grape grape_2 grass_rock grass_rock_cliff great_sword green_maple_tree greenhouse gremlin gremlin_nob grocery_box ground_item guard guard_tower guardian hammer hamster harpy hay_bale hay_bale_2 heal_pickup held_item helicopter hero_necromancer hero_warrior hexaghost horned_beast horse hospital_assistant house hungry_mouse hunter hunter_bow hunting_hammer hut ice_shard idle_character invoker_mage iron_node item_crate jam_jar jaw_worm jetski kid killer knife knight lab_crate_a ladder ladder_a lagavulin lamp lamp_2 lance lane lantern lantern_2 light_pole lighthouse_keeper lion log_pile log_pile_2 looter louse lumber_camp machine_gun mage magnet market_stall maw mecha_chameleon medic medicine_crate mil_supply_set mine miner minotaur moss_boar motorcycle mushroom mystic nemesis nephew niece night_watchman ninja_runner npc nurse oak_tree officer oil_drum oil_lamp owl paddle palm_tree palm_tree_2 parasite patient pearl_bush pebble person person_2 pickaxe pickup_orb pig pine_tree pistol pistol_2 plate plate_2 player_char police_car pond poop projectile pumpkin pyramid pyramid_2 rabbit rabbit_runner radio_console ragdoll_fighter rain_mill rain_totem ranger requiem_gun resident revolver rifle road_sign robot_grunt robot_runner rock rocket rocket_launcher rocksample rope rope_bundle rowboat rowboat_2 runic_obelisk runic_obelisk_2 sandbag sawmill scavenger school scifi_mil_crate sea_mine sedan sedan_2 sentinel_drone sentry shark sheep shield shipping_container shop_counter shop_floor shopkeeper shotgun shower shuriken signpost signpost_2 signpost_3 skeleton skull slaver slime slime_boss small_rock smg smoke_bomb snail snecko sniper_rifle sofa soil_tile soldier spear spider sports_car squid stair star star_core statue stepmother stilt_house stone_pile stonecutter_lodge storehouse storm_hearth storm_shrine supply_crate surgeon survivor sword table tank_hull tannery taskmaster telephone television tent timber_bundle time_eater tire toaster toaster_2 tool_chest torch torch_2 torus tower training_dummy training_dummy_2 transient travel_pack tree tribal_cat_elder tribal_cat_villager trophy trunk turret uncle villager volleyball wagon wall warehouse watchtower water_barrel water_well watercan watering_can weapon_rack weapon_rack_2 weapon_shop_sign well wheat wheelbarrow white_tribal_cat_player wizard wizard_staff wolf wolf_2 wood_fence wood_fence_2 wooden_stool worker_robot zebrite_totem zombie
62
+ image: acid_slime_m acid_slime_s acolyte airtank_icon ak_rifle_icon alien_bug angled_player_banner aorta ascended_one awakened_one axe_icon banana_weapon_icon bandage_icon banshee barracks_icon baseball_bat_icon bean_icon bean_seeds_icon bear berries_icon blue_slaver bone_icon book_icon boss_skull bread_icon bullet_icon cabbage_icon campfire_icon canned_food_icon card_all_in card_bad_beat card_bargain card_borrowed_nerve card_brace card_charge card_chipped_edge card_cinch card_cleave card_dead_man_hand card_discipline card_double_slash card_doubling_down card_escalating_debt card_execute card_flail card_focus card_fortify card_fortune card_hex card_hexed_needle card_house_rules card_hunker card_inverted_wager card_last_stand card_ledger_balance card_loaded card_long_odds card_lunge card_marked_ledger card_mend card_pawn_the_ward card_pick_the_lock card_pray card_rage card_read_the_table card_reckless_cleave card_reversal card_scavenge card_sharpen card_shatter card_shave_the_die card_shield card_skull_crusher card_slash card_smash card_snap_shot card_snipe card_strain card_tallow_draught card_temper card_the_long_count card_the_whole_table card_tin_ward card_two_fingers card_unleash card_weaken card_weighted_coin card_whirlwind carrot_icon cave_rat cavern_battleground ceaseless_statue cheese_icon chrono_wisp cinder_fiend clock_icon clockwork_hound coconut_icon coin_icon corrupt_heart crop_icon crossed_swords crossed_swords_2 crosshair crowbar_icon cultist cultist_acolyte cute_paw_cursor cutthroat dagger darkling darkling_spawn dash_icon deca deck_icon deck_icon_2 donu energy_icon exploder factory_icon fat_gremlin feather_icon fish_bone_icon fish_icon fishing_rod_icon flamethrower_icon flint_icon fluffy_sheep_party_portrait foe_bone_abacus foe_chain_warden foe_coin_shill foe_marrow_cantor foe_needle_witch foe_tallow_priest foe_the_gutterhound foe_the_hexwright foe_the_house foe_wax_cur forest_ancient_tree_spirit_wide_splash forest_ent fungi_beast fungi_sporeling gnawing_mass goblin goblin_brute gold_coin_icon gold_icon gold_icon_2 green_louse gremlin_fat gremlin_leader gremlin_sneaky gremlin_wizard grenade_launcher_icon guardian_mech hand_icon harvester_icon health_icon health_icon_2 hero_necromancer hero_warrior hexaghost hide_icon hoe_icon honey_icon horse_party_portrait hu_armor hu_headshot hu_hit hu_hitcrit hu_mortar hu_recon hu_reload hu_rifle hu_sentry hud_armor_plate_shield hud_assault_rifle_side hud_crit_hitmarker_x_red hud_headshot_skull hud_hitmarker_x_ticks hud_mortar_strike_shell hud_reload_circular_arrow hud_sentry_turret hud_uav_recon_drone hud2_drone_wing hud2_reload_arrow hud2_rifle_flat hud2_shell_burst hud2_turret_gun hud2_x_marker hud2_x_marker_red hud3_drone_stencil hud3_rifle_stencil hud3_shell_stencil hud3_skull_stencil hunger_icon intent_attack intent_block intent_curse inverse_trapezoid_top_bar iron_ore_icon jaw_worm knife_icon knight_helmet lagavulin laser_gun_icon leaf_icon lich loading_splash log_icon longodds_hall longodds_table looter machine_gun_icon mackerel_icon mad_gremlin maw metal_cast_icon mold_icon monster_head moon_icon nemesis ninja_runner node_boss node_elite node_fight node_rest node_shop obsidian_icon obsidian_sentry orange_cat_party_portrait orb_walker orc panel_frame parsnip_icon parsnip_seeds_icon pickaxe_icon pill_bottle_icon pistol_icon plank_icon player_debtor poison_icon potato_icon potato_seeds_icon power_icon purple_hive_mind_insect_warrior_wide_splash qa_probe_gem radish_icon red_louse red_slaver refinery_icon reptomancer repulsor resin_icon revolver_icon rifle_icon road_tiles_icon road_tiles_icon_2 robot_face robot_vending_mech_champion_wide_splash rocket_launcher_icon rope_icon round_table_knight_wide_splash roundabout_icon roundabout_icon_2 roundabout_ring_icon_transparent_no_background ruby_gem_icon salvage_icon sanity_icon sardine_icon scythe_icon seed_bag_icon seed_icon sentry shield_gremlin shotgun_icon silent skeleton skeleton_archer skill_dagger_icon skill_fireaura_icon skill_hammer_icon skill_shield_icon skill_stealth_icon slaver slime_boss slime_horror snake_dagger snapper_icon sneaky_gremlin snecko snecko_hatchling sniper_rifle_icon snowflake_spin soda_can_icon spider spike_slime spike_slime_l spike_slime_m spike_slime_s spiker spire_acolyte spire_shard spire_shield spire_spear squid_icon steak_icon stick_icon stone_icon straight_road_icon_transparent_no_background submachine_gun_icon sun_icon syringe_gun_icon tank_icon taskmaster tft_alien_portrait_black tft_boss_skull_black tft_clock_black tft_crossed_swords_black tft_forest_portrait_black tft_monster_head_black tft_robot_portrait_black tft_yokai_portrait_black time_eater torch_icon traffic_light_icon traffic_light_icon_2 traffic_light_icon_transparent_no_background transient tuna_icon turret_icon twigs_icon undead_death_knight_wide_splash undead_skull vampire ventricle water_bottle_icon water_drop_icon watering_can_icon werewolf white_chicken_party_portrait witch withered_titan wolf wood_icon yokai_mask yokai_samurai_oni_wide_splash
63
+ sound: ambient_horror apartment_ambience arena_warning baby_raptor_step backfire ball_hit banana_boomerang baton_hit baton_swing bear_shotgun bite blade_hit blast body_thump body_thump_2 boost boost_burst bow_hit branch_snap brass_gong cannon_blast cannon_fire car_horn car_horn_2 card_cast card_draw card_play cash_pickup cash_pickup_2 character_select checkpoint chomp civilian_scream civilian_scream_2 coin_drop coin_pickup coin_pickup_2 core_collect corn_burst correct_chime countdown countdown_beep countdown_tick crab_blaster crab_hit crab_skill crash crash_burst critical_impact critical_impact_2 croc_machinegun crow_call crowd_cheer crystal_pickup dash_burst dice_rattle dodge dodge_dust dodge_dust_2 dog_bark dog_pistol door_close door_creak door_open door_open_2 door_unlock dual_hit earthquake_rumble earthquake_rumble_2 electric_burst electric_shock eliminated eliminated_2 enemy_attack energy_empty engine_acceleration engine_idle exit_unlock explosion finish finish_chime fish_caught fishing_cast fishing_reel flamethrower_burst flare_pop foe_die fog_horn food_complete food_complete_2 front_weapon_activation fusion_burst game_check game_end game_start gameplay_music gameplay_music_2 generator_start glimmer_bell gold_pickup golf_cup golf_swing grenade_launcher gunshot hammer_hit hammer_hit_2 heal_glow heartbeat hit hook_impact hook_impact_2 hook_throw hook_throw_2 item_drop item_pickup item_pickup_2 jet_engine laser_shot laser_shot_2 lasgun_shot level_up level_up_2 level_up_3 level_up_4 level_up_5 lighthouse_bell line_clear machine_gun machine_gun_reload machine_gun_shot magic_blast medkit_pickup medkit_use menu_music metal_hit metal_hit_2 mine missile_launch missile_warning mole_sniper monster_attack monster_attack_2 monster_roar monster_roar_2 owl_hoot pickup piece_capture piece_lock piece_move pistol_reload pistol_reload_2 pistol_shot pistol_shot_2 place player_hit player_hurt player_hurt_2 police_siren punch punch_2 rain_ambience raptor_step ready_up rear_ability_activation relic_click reload repair resource_pickup respawn respawn_2 revive rifle_reload rifle_shot rifle_shot_2 ring_out ringout robot_beam robot_blast robot_charge robot_laser rock_riff rocket_shot rot_loop round_start royal_capture score_boost shield shield_block ship_destroyed ship_horn shotgun_shot signal_ping siren slab_turn slap slap_hit speed_burst stone_rotate storm_thunder survivor_step sword_clang sword_clang_2 sword_hit sword_hit_2 sword_swing tire_skid toast_pop toy_pop trex_step turn_alert turn_bell ui_click ui_click_2 ui_click_3 ui_click_4 ui_click_5 vehicle_damaged vehicle_destroyed victory victory_fanfare victory_fanfare_2 victory_sting victory_sting_2 vortex_hum wall_deploy wall_pickup ward_up warning_bell warning_siren water_splash wave_crash wave_start weapon_switch whistle wind_gust wind_gust_2 wolf_howl wood_impact wrong_buzz zebrite_chime zombie_attack zombie_growl zombie_roar
64
+ effect: acid_splash arcane_cast arcane_impact arrow_hit bite_hit bite_splash blood_hit blood_splash boost_burst burner burner_2 burner_3 burner_4 burner_5 burner_6 burner_7 burner_8 burner_9 cannon_blast capture_burst cartoon_impact cash_sparkle cash_sparkle_2 coin_sparkle collapse_dust core_pickup crash_burst critical_smoke damage_smoke damage_sparks damage_sparks_2 damage_sparks_3 dash_trail dash_trail_2 dismember dodge_dust dust_puff dust_puff_2 dust_puff_3 electric_burst electric_sparks elemental_burst eliminated_burst enemy_hit energy_bolt evolution_burst explosion explosion_burst finish_confetti fire_burst fireball fireball_burst ghost_mist gold_rush golf_cup golf_hit grenade_blast hammer_impact heal_glow healing_glow hit_burst hit_burst_2 hook_hit hook_hit_2 ice_shard impact_flash_critical impact_flash_small impact_shockwave impact_shockwave_2 impact_shockwave_3 impact_sparks impact_sparks_2 ink_cloud item_glow lightning_burst lightning_strike magic_blast magic_sparkle magic_vortex metal_hit meteor_fire mine_burst muzzle_flash muzzle_flash_2 objective_pickup player_hit poison_cloud rage_burst rainbow_burst rainbow_burst_2 rapid_slash repair_sparks respawn_burst respawn_burst_2 revive_burst revive_glow rickshaw_burn ringout_explosion rocket_explosion rot_pulse rune_pickup shield_burst shield_pop slap_hit slash_burst slash_burst_2 slash_burst_3 smoke smoke_puff smoke_puff_2 sonar_pulse sparkle_burst sparkle_burst_2 sparkle_burst_3 sparkler speed_burst steam_burst step_dust stun_stars stun_stars_2 stun_stars_3 sword_hit sword_slash tank_burning tank_explosion terrain_blast top_collision_impact w_firethrowe w_firethrust_finale_2026 water_burst water_splash wind_burst wind_leaves
56
65
  flipbook: blood_fine blood_mist blue_flame bubble_float dust_fine_grey dust_puff electric_spark ember_glow explosion_burst explosion_soot gray_smoke heart_pulse hot_spark_burst leaf_flutter magic_sparkle muzzle_cone muzzle_flash_star orange_flame poison_cloud smoke_puff smoke_soft_grey snowflake_spin spark_hot_grey star_twinkle tracer_streak water_splash white_smoke
57
66
  </asset_word_library>
58
67
 
59
68
  <texture_dsl_usage>
60
69
  - For new worlds using primitive meshes, use texture dsl to paint them.
61
70
  - `#texture Name ... #end` auto-creates triplanar `material Name`. Use it as `material = "Name"` in scene objects, `Material.Name` in Weave code, `terrain.SetTerrainMaterial(Material.Name)`, `terrain.SetTerrainLayers(...)`, `batch.SetBatchMaterial(Material.Name)`, or as a local `#mesh` slot name (`material: "Name"` / `paint(..., material: "Name")`).
62
- - Masonry patterns (pattern.brick/tile) under the default triplanar mapping project world-space joints; on carved/round geometry (columns, moldings, statues) use a coursing-free variant or `mapping = uv;`.
71
+ - Masonry patterns (pattern.brick/tile) under triplanar mapping project straight planar joints; on carved/round geometry (columns, moldings, statues) use a coursing-free variant or `mapping = uv;`.
63
72
  - Flat color shorthand: `#texture TeamRed color=#B03A2E` creates a non-emissive triplanar `material TeamRed` — for stylized color-blocking and quick placeholders; surfaces meant to read as real should use the built-in library (next clause). Optional `roughness=0..1` and `metallic=0..1` (e.g. `#texture RubberFloor color=#1E2024 roughness=0.95`) — without a high roughness, dark ground colors read pale from sky reflection.
64
73
  - Built-in material library — real textured PBR with zero declarations: `material = "Wood"` on any object/batch/terrain (and `Material.Wood` in code) just works; the engine copies the one-line library clause into the world on save, and a world-declared material of the same name always wins. Library: Wood, WoodDark, Plank, Bark, Stone, Rock, Brick, Dirt, Sand, Metal, MetalDark, Gold, Fabric, Cloth, Rope, Straw, Leather, Foliage, Leaf, Grass, Snow, Ceramic, Bone, Glass, GlassTransparent, Water, Default; authored surfaces StylizedBricks, OldTilesWhite, StylizedGrass, PolishedConcrete, Asphalt. Default to these for anything meant to read as a real surface.
65
74
  - Derived material (color variant, no extra texture bake): `material WoodRed { base = "Wood" tint = "#8a2b1e" }` copies shader/textures/params from the base material and multiplies its color by tint (quoted hex). `base` may name any built-in above or a tintable near-white detail family (WoodDetail, StoneDetail, MetalDetail, FabricDetail, LeatherDetail, PlasterDetail). Prefer this over writing a `#texture` body when only the color differs.
66
75
  Material clauses also accept `emission = N` (self-lit glow in the material's tinted color; linear: 1 soft, 4 lamp, 16+ neon bloom) and `roughness = 0..1` / `metallic = 0..1` (uniform overrides of the baked surface response) — e.g. `material CyanGlow { base = "MetalDetail" tint = "#19D9FF" emission = 5 }`.
67
76
  - Use #texture directly on primitives, terrain, batches, and local `#mesh` slots. For #mesh material precedence: object/batch override > explicit submesh override > authored local mesh slot material > intrinsic GLB PBR. Imported GLB material names are not auto-bound to world materials. `tile_m` = meters per pattern repeat (default 1.0, prop scale): RAISE to `2.0;`..`4.0;` for large terrain/floors so repeats are not too tiny; LOWER to `0.3;`..`0.5;` for small props/panels so the pattern does not print larger than the object. Material clauses accept it too (`material X { base = "Fabric" tileM = 0.4 }`).
77
+ - Pixel-art / retro texel look: `filter = nearest;` samples the baked maps with hard texel edges (no bilinear smoothing, no block compression). Pair with a small bake (`size = 16;`..`64;`) or constant-cell patterns, and usually `mapping = uv;`. Everything else keeps the default linear filtering.
68
78
  - Authoring or editing a `#texture` BODY (anything beyond the color=/base= one-liners above): read the `texture_dsl` skill first — statement syntax, builtins (noise/pattern/sdf/filter families), recipes, worked examples. Do not guess builtin names or signatures from memory.
69
79
  - Terrain layer sets: assign scalar `out.height` on every layer of the set (or none) — higher-height texels win at SetTerrainLayers blend borders (rock pushing through grass).
70
80
  - `#terrain_splat Name ... #end` + `terrain.SetTerrainSplat("Name")`: same expression language with terrain inputs `height` (world m), `slope01` (0 flat..1 vertical), `convex` (-1..1), `ao` (0..1), `moist` (0..1), `user` (0..1, fed by `terrain.SetTerrainSplatMask(mask, sx, sy)`, else 0); `out.color` RGB = SetTerrainLayers Steep/High/Low weights (base = leftover). Baked per-terrain; replaces the slope/height auto-bands for hand-authored placement (e.g. `out.color = vec3(smoothstep(0.4, 0.6, slope01), smoothstep(18, 24, height), sat(moist * 2 - convex));`).
71
- - Optional scalar `out.grass` in `#terrain_splat` = grass spawn density 0..1 (0 = bare; spawn saturates around 0.4, so mid values still read as full cover) - keeps grass off beaches/cliffs/dry zones from the same rules that place the textures, and thins the auto ground tint with it. Unassigned = full density; an explicit SetTerrainGrassWeightmap overrides it.
72
- - Colormap mode: `out.albedo` (vec3, mutually exclusive with `out.color`) makes the bake the FINAL surface color instead of layer weights - full per-pixel control (satellite-style coloring, stylized palettes, drainage streaks from `user`). The base SetTerrainMaterial still supplies normal/ORM detail; SetTerrainLayers weights, band blending, macro tint and the auto grass ground tint are all bypassed. Set `size = 2048;` (up to 4096) for crisp color; `out.grass` still works alongside.
81
+ - Optional scalar `out.grass` in `#terrain_splat` = grass spawn density 0..1 (0 = bare; cover saturates around 0.4, and below ~0.5 grass breaks into clumps whose edge tufts shrink and lose blades, so a painted gradient ends in wispy patches, not a hard line) - keeps grass off beaches/cliffs/dry zones from the same rules that place the textures. Unassigned = full density; an explicit SetTerrainGrassWeightmap overrides it.
82
+ - Colormap mode: `out.albedo` (vec3, mutually exclusive with `out.color`) makes the bake the FINAL surface color instead of layer weights - full per-pixel control (satellite-style coloring, stylized palettes, drainage streaks from `user`). The base SetTerrainMaterial still supplies normal/ORM detail; SetTerrainLayers weights, band blending and macro tint are all bypassed. Set `size = 2048;` (up to 4096) for crisp color; `out.grass` still works alongside.
73
83
  - `#road_network Name ... #end`: a road GRAPH the engine meshes into junction-merged road tiles at load (carriageway, miter-blended intersections, curbs, sidewalks, center dashes, crosswalks). Body statements end with `;`: `cell = 32000;` (tile grid cm, optional), `class = width_cm, sidewalk_cm, curb_w_cm, curb_h_cm, "CarriagewayMat", "CurbMat", "SidewalkMat", "MarkingMat";` (repeatable; edges index classes in order; empty marking name = no markings), `nodes = x, y, x, y, ...;` (cm, repeatable/appends), `edges = nodeA, nodeB, classIndex, ...;` (triplets, repeatable), `curve = a, b, c1x, c1y[, c2x, c2y];` (optional per a→b edge: quadratic/cubic bezier control points in cm; omit = straight; junctions meet curves at their end tangents; too-tight curvature for the class width is a load error). The block spawns the network itself: tiled road meshes plus walkable per-edge slabs (no companion object needed); optional `position = x, y, z;` offsets the whole network. Editor edits fold back into this block on save — re-read it before patching. Prefer this over hand-built per-segment road boxes: junctions come out clean and one graph edit reflows everything.
74
84
  - `#terrain_bytes "terrain.url" ... #end`: embedded uploaded-terrain heightfield payload (base64 of zlib-compressed terrain bytes) satisfying `terrain X { model = "terrain.url" }` with zero network fetches, so the world is self-contained offline. Blocks are machine-generated (desktop `embed_terrain` CLI), not hand-authored - and never required to submit: validation resolves uploaded `terrain.` assets itself. Prefer procedural terrain (`SpawnTerrainFromHeights` + `TerrainHeights*`) for new worlds.
75
85
  - Emission is linear (1 soft self-lit, 4 lamp, 16+ neon bloom): put it on small accents - lamps, signs, runes, windows; large emissive surfaces wash out the scene, keep those <= 1.
@@ -77,13 +87,13 @@ flipbook: blood_fine blood_mist blue_flame bubble_float dust_fine_grey dust_puff
77
87
  </texture_dsl_usage>
78
88
 
79
89
  <adjuster_usage>
80
- - `#adjuster ... #end` (top-level): declares hand-tunable global look knobs. Values apply on load; slider edits fold back into this block on save. Sliders: grass/ground knobs in the Terrain inspector (click the terrain), SetWater* in Find > Water, the rest in the Adjust panel (top-right sliders icon; links to both inspectors).
81
- - Body lines: `section "Title"` groups knobs after it; `knob <SimCallName> = <value> [range a..b]`; value is a number, or `#rrggbb` for color knobs; `range` overrides the slider range of number knobs. `//` comments ok.
90
+ - `#adjuster ... #end` (top-level): declares hand-tunable global look knobs. Values replay at load end - an OnSpawned Set* of the same value overwrites the user's tune every load (event-driven changes later are fine); slider edits fold back into this block on save. Sliders: grass/ground knobs in the Terrain inspector (click the terrain), SetWater* in Find > Water, the rest in the Adjust panel (top-right sliders icon; links to both inspectors).
91
+ - Body lines: `section "Title"` groups knobs after it; `knob <SimCallName> = <value> [range a..b] [user]`; value is a number, or `#rrggbb` for color knobs; `range` overrides the slider range of number knobs; a trailing `user` marks the line user-LOCKED (see below). `//` comments ok.
82
92
  - Only whitelisted global sim calls (below) are valid knob names; each at most once. Texture params and ranged #mesh params expose per-texture/per-mesh panel knobs on their own; no #adjuster entry for those.
83
93
  - Add an #adjuster block whenever the look is a matter of taste the user may want to hand-tune (grass, ground, water, wind, tree LOD).
84
- - Lighting/sky/grade knobs (sun color/intensity, skybox tint/saturation/exposure, SSAO, vignette, scene contrast/saturation/exposure) are USER-owned: never write or retune them, as knobs or calls; lighting/mood requests -> Adjust panel.
85
- - SetWater* knobs set the INITIAL ocean state: they replay after load-time code, so a `knob SetWaterEnabled = 0` line keeps water off across reloads (the editor's Find > Water delete writes it). Code that changes water after load (OnSpawned, timers, events) still wins at runtime.
86
- - Knobs: SetGrassRootColor (color), SetGrassTipColor (color), SetGrassBrightnessVariationPercent (0..300), SetGrassSizeVariationPercent (0..300), SetGrassHeightMinCm (0..300), SetGrassHeightMaxCm (0..300), SetGrassWavePeriodCm (100..3000), SetGrassWindStrengthCm (0..50), SetGrassWindSpeedPercent (0..300), SetGrassBendStrengthPercent (0..200), SetGrassBladeWidthPercent (50..200), SetGrassSpacingCm (0..100), SetGrassTerrainActiveDistanceM (0..500), SetGrassCullDistanceM (0..500), SetGrassFadeDistancePercent (0..100), SetGrassFarThinningPower (0..8), SetTerrainErosionDetailPercent (0..300), SetSunColor (color), SetSunIntensity (0..20), SetSunRotation (0..360), SetSunTilt (0..90), SetSkyLightIntensity (0..8), SetSkyLightColor (color), SetSkyLightSaturation (0..3), SetSkyboxTint (color), SetSkyboxSaturation (0..3), SetSkyboxExposure (0..3), SetSkyboxHaze (0..8), SetSkyboxContrast (0..3), SetSkyboxGroundColor (color), SetSkyboxColor (color), SetSkyboxCloudCoverage (0..1), SetSkyboxStarIntensity (0..3), SetShadowBlurRadius (0..20), SetShadowOpacity (0..1), SetShadowLift (0..3), SetShadowLiftThreshold (0.02..0.3), SetShadowDistance (0..500), SetFogColor (color), SetFogStartDistance (0..2000), SetFogEndDistance (0..2000), SetFogSkyboxAffect (0..100), SetHeightFogEnabled (0..1), SetHeightFogTop (0..200), SetHeightFogBottom (-100..200), SetHeightFogSoftness (0..1), SetSsaoIntensity (0..1), SetSsaoDistance (0..10), SetBloomThreshold (0..3), SetBloomIntensity (0..2), SetTerrainNormalStrengthPercent (0..300), SetContrast (0..4), SetSaturation (0..4), SetExposure (-3..3), SetVignetteStrength (0..1), SetVignetteRadius (0..1), SetVignetteSoftness (0..1), SetVignetteColor (color), SetWaterEnabled (0..1), SetWaterSeaLevelCm (-2000..10000), SetWaterAmplitudeCm (0..500), SetWaterWavelengthCm (100..20000), SetWaterSteepness (0..1), SetWaterDirectionDeg (0..360), SetWaterSpeedPercent (0..400), SetWaterShallowColor (color), SetWaterDeepColor (color), SetWaterColorMixRangeCm (1..2000), SetWaterTransparency (0..1), SetWaterMurkDensity (0..4), SetWaterReflectionStrength (0..2), SetWaterReflectionDistortion (0..2), SetWindDirectionDeg (0..360), SetWindStrength (0..1), SetWindGustiness (0..1), SetTreeLodSwitchDistanceM (0..300), SetTreeMeshDistanceM (0..1000), SetTreeCullDistanceM (0..3000), SetTreeFadeDistancePercent (5..80), SetTreeFarLeafDensityPercent (10..300)
94
+ - Knobs marked [user] in the list below are Adjust-panel lighting/grade: when the USER tunes one, its line gains a ` user` lock marker and patches retuning, removing, or unlocking that LINE are rejected (their tune is sovereign) - retune locked knobs only via the propose_adjust_values tool (an [Apply lighting] card; only the user's click applies). UNMARKED [user] knob lines - including delegated mood work and fresh worlds - are yours to author as knobs (never as Set* calls: an OnSpawned Set* overwrites the panel every load). Never write or strip a ` user` marker yourself. Dark/moody art direction also works without grade knobs: lights, emissive materials, fog (SetFog* calls/knobs are game-ownable), enclosure, SetVolumetricLightIntensity.
95
+ - SetWater* knobs set the INITIAL ocean state: a `knob SetWaterEnabled = 0` line keeps water off across reloads (the editor's Find > Water delete writes it).
96
+ - Knobs: SetGrassRootColor (color), SetGrassTipColor (color), SetGrassBrightnessVariationPercent (0..300), SetGrassSizeVariationPercent (0..300), SetGrassHeightMinCm (0..300), SetGrassHeightMaxCm (0..300), SetGrassWavePeriodCm (100..3000), SetGrassWindStrengthCm (0..50), SetGrassWindSpeedPercent (0..300), SetGrassBendStrengthPercent (0..200), SetGrassBladeWidthPercent (50..200), SetGrassSpacingCm (0..100), SetGrassTerrainActiveDistanceM (0..500), SetGrassCullDistanceM (0..500), SetGrassFadeDistancePercent (0..100), SetGrassFarThinningPower (0..8), SetTerrainErosionDetailPercent (0..300), SetSunColor (color) [user], SetSunIntensity (0..20) [user], SetSunRotation (0..360), SetSunTilt (0..90), SetSkyLightIntensity (0..8) [user], SetSkyLightColor (color) [user], SetSkyLightSaturation (0..3) [user], SetSkyboxTint (color) [user], SetSkyboxSaturation (0..3) [user], SetSkyboxExposure (0..3) [user], SetSkyboxHaze (0..8) [user], SetSkyboxContrast (0..3) [user], SetSkyboxGroundColor (color) [user], SetSkyboxColor (color) [user], SetSkyboxCloudCoverage (0..1), SetSkyboxStarIntensity (0..3), SetShadowBlurRadius (0..20) [user], SetShadowOpacity (0..1) [user], SetShadowLift (0..3) [user], SetShadowLiftThreshold (0.02..0.3) [user], SetShadowDistance (0..500), SetFogColor (color), SetFogStartDistance (0..2000), SetFogEndDistance (0..2000), SetFogSkyboxAffect (0..100), SetHeightFogEnabled (0..1), SetHeightFogTop (0..200), SetHeightFogBottom (-100..200), SetHeightFogSoftness (0..1), SetSsaoIntensity (0..1) [user], SetSsaoDistance (0..10), SetBloomThreshold (0..3) [user], SetBloomIntensity (0..2) [user], SetTerrainNormalStrengthPercent (0..300), SetContrast (0..4) [user], SetSaturation (0..4) [user], SetExposure (-3..3) [user], SetVignetteStrength (0..1) [user], SetVignetteRadius (0..1) [user], SetVignetteSoftness (0..1) [user], SetVignetteColor (color) [user], SetWaterEnabled (0..1), SetWaterSeaLevelCm (-2000..10000), SetWaterAmplitudeCm (0..500), SetWaterWavelengthCm (100..20000), SetWaterSteepness (0..1), SetWaterDirectionDeg (0..360), SetWaterSpeedPercent (0..400), SetWaterShallowColor (color), SetWaterDeepColor (color), SetWaterColorMixRangeCm (1..2000), SetWaterTransparency (0..1), SetWaterMurkDensity (0..4), SetWaterReflectionStrength (0..2), SetWaterReflectionDistortion (0..2), SetWindDirectionDeg (0..360), SetWindStrength (0..1), SetWindGustiness (0..1), SetTreeLodSwitchDistanceM (0..300), SetTreeMeshDistanceM (0..1000), SetTreeCullDistanceM (0..3000), SetTreeFadeDistancePercent (5..80), SetTreeFarLeafDensityPercent (10..300)
87
97
  Example:
88
98
  #adjuster
89
99
  section "Grass"
@@ -94,15 +104,16 @@ knob SetGrassHeightMaxCm = 45 range 0..120
94
104
 
95
105
  <particles_json_usage>
96
106
  - `#particles Name ... #end` defines local effect asset `effect.Name`; `#particles effect.Name` also works, but prefer short `Name`. Body is a JSON array; each object is one emitter component.
97
- - Every local component needs a top-level `"particleTextureName"`: `"flipbook.<word>"` for an animated sprite-sheet atlas from the shared flipbook library (best for fire/smoke/magic; lowercase subject words like `flipbook.orange_flame`; misses render a glow placeholder until the word is minted), `"texture.TextureName.color"` with a matching `#texture TextureName`, or `"image.<User>.<Name>"` referencing a real ori image asset you actually know (never invent one). Use `texture.*.color` (`baked.*` addresses material-slot maps, not particle textures; `.normal`/`.orm` are not particle flipbooks).
98
- - Spawn with scene `effect Spark { effect = "effect.Name" }` - or, for prop-attached fire/smoke, prefer a `particles("<word>", at: ...)` socket inside the `#mesh` (see <mesh_dsl_usage>). From code pass the ASSET as a static import: `SpawnEffectWithAutoDestroy(Ori.effect.Name, pos)` (returns the spawned object — `.SetSizeUniform(n)` to size it), or `Ori.alias.effect.<word>` for aliases. A `Scene.<object>` reference is not an effect asset (W2C0405).
107
+ - Every local component needs a top-level `"particleTextureName"` (components binding `"material"` may omit it): `"flipbook.<word>"` for an animated sprite-sheet atlas from the shared flipbook library (best for fire/smoke/magic; lowercase subject words like `flipbook.orange_flame`; misses render a glow placeholder until the word is minted), `"texture.TextureName.color"` with a matching `#texture TextureName`, or `"image.<User>.<Name>"` referencing a real ori image asset you actually know (never invent one). Use `texture.*.color` (`baked.*` addresses material-slot maps, not particle textures; `.normal`/`.orm` are not particle flipbooks).
108
+ - Spawn with scene `effect Spark { effect = "effect.Name" }` - or, for prop-attached fire/smoke, prefer a `particles("<ref>", at: ...)` socket inside the `#mesh` (see <mesh_dsl_usage>). From code pass the ASSET as a static import: `SpawnEffectWithAutoDestroy(Ori.effect.Name, pos)` (returns the spawned object — `.SetSizeUniform(n)` to size it), or `Ori.alias.effect.<word>` for aliases. A `Scene.<object>` reference is not an effect asset (W2C0405). For a muzzle flash or any follow-effect: `SpawnEffectAttached(Ori.effect.Name, holder, "muzzle", 0)` - never chase with per-frame SetPosition.
99
109
  - Sizing: declare `"boundingBoxSize": [x, y, z]` (meters, Z-up; per-component, max wins) as the effect's authored real-world footprint, and author emitters (`radius`, `sizeSequence`, speeds) at that real scale. The object's `size` (cm, like any object) then scales the effect by `size / boundingBoxSize` — e.g. a campfire authored with `boundingBoxSize [1, 1, 1.5]` placed with `size = 100` renders at its authored 1m footprint. Without `boundingBoxSize` the base is a legacy 10cm box, so `size = 100` blows the effect up 10x — always declare it. Size effects with `size`, not `scale`. Author emitters freely around your origin; when the content is not origin-centered, add `"boundingBoxCenter": [x, y, z]` (meters; where the box CENTER sits relative to that origin; default [0,0,0]) so the box covers the content — e.g. a ground flame rising 0..1.5m declares [0, 0, 0.75]. Placement matches meshes: the object's `position` rests the declared box's bottom on the point (`transform_pivot = center` centers it); code spawns (`SpawnEffect*`) put the box CENTER at the given position.
100
110
  - JSON values: scalars are numbers, vecs are `[x,y]`/`[x,y,z]`, curves are up to 8 `[[life01, value], ...]`; `colorSequence` uses `[[life01, [r,g,b]], ...]`. Prefer numeric `0/1` for booleans.
101
111
  - CPU/import keys: `_particleName_` ignored label; `particleCount` live-particle capacity override, not spawn count; `emitterPosition`/`emitterRotation` component-local offset/XYZ degrees; `boundingBoxSize`/`boundingBoxCenter` authored footprint + box placement in meters (see sizing bullet above).
102
112
  - Flipbook keys: `framesX`, `framesY`, `totalFrames`, `flipbookAnimationSpeed`, `flipbookStartRandom`. For `flipbook.<word>` refs these auto-fill from the library at load; set them only to override.
113
+ - Custom shading: `"material": "M"` (billboard components; material with a `#shader`) replaces the texture sample with that shader's fragment - SDF shapes (crisp at any size), dissolves, code-driven looks. Body writes `out.color` from ctx (`life01`, `flipbook_color`, `tint`, ...) - contract in the shader_dsl skill; the other component keys (lit, additiveBlend, sequences, fades) still apply.
103
114
  - Spawn keys: `spawnStartDelay`, `spawnRate` per sec, `isLooping`, `duration` seconds (`0` means no shader stop; use finite duration for one-shots), `burstParticleCount` (fires at t=0, then every `burstInterval` seconds while emitting; `burstInterval` unset = the t=0 burst only).
104
- - Shape keys: `emitterShapeType` = `"sphere"|"box"|"cylinder"|"disc"` or `0..3`; `shapeStyle` = `"volume"|"surface"`; `shapeInOut` = `"in"|"out"|"both"` (omitted defaults inward); `radius`, `cylinderHeight`, `boxSize`, `shapePartial`, `lifeTimeRange`, `speedRange`, `spreadAngle` `[yaw,pitch]` degrees, `lockedToEmitter` (`0` default = particles keep world position once spawned - trails stay behind a moving emitter; `1` = particles ride it, e.g. torch flame). Box surface emits from +Z face; cylinder height is local X; disc uses `cylinderHeight` as thickness. Emission direction: each shape emits along its own radial direction; `shapeInOut` omitted = INWARD, `"out"` flips outward, `"both"` = 50/50. `spreadAngle [yaw, pitch]` then jitters that direction by uniform +/-yaw degrees about the emitter's local Y axis and +/-pitch degrees about its X axis (fixed local axes, not a cone around the direction).
105
- - Visual/motion keys: `sizeConstant` or `sizeSequence`; `opacityConstant` or `opacitySequence`; `colorConstant` or `colorSequence`; `accelerationVector` authored Z-up; `drag` seconds to halve velocity; `rotationRange`, `rotationSpeedRange`; `orientation` 0 facing camera / 1 world-up / 2 velocity-parallel / 3 velocity-perpendicular; `additiveBlend`, `isHidden`, `softParticleFadeDisabled`, `softParticleFadeDistance`, `squashConstant` or `squashSequence` (positive stretches up, negative sideways); `lit` 1 = sun+sky shading (smoke/dust/debris read as lit volumes; leave 0 for emissive fire/glow).
115
+ - Shape keys: `emitterShapeType` = `"sphere"|"box"|"cylinder"|"disc"` or `0..3`; `shapeStyle` = `"volume"|"surface"`; `shapeInOut` = `"in"|"out"|"both"` (omitted defaults inward); `radius`, `cylinderHeight`, `boxSize`, `shapePartial` (fraction of the shape, default 1 = full; sphere: polar cap toward +Z, cylinder: 1 cylinder -> 0 cone, disc: 1 full disc -> 0 rim ring), `lifeTimeRange`, `speedRange`, `spreadAngle` `[yaw,pitch]` degrees, `lockedToEmitter` (`0` default = particles keep world position once spawned - trails stay behind a moving emitter; `1` = particles ride it, e.g. torch flame). Box surface emits from +Z face; cylinder height is local X; disc uses `cylinderHeight` as thickness. Emission direction: each shape emits along its own radial direction; `shapeInOut` omitted = INWARD, `"out"` flips outward, `"both"` = 50/50. `spreadAngle [yaw, pitch]` then jitters that direction by uniform +/-yaw degrees about the emitter's local Y axis and +/-pitch degrees about its X axis (fixed local axes, not a cone around the direction).
116
+ - Visual/motion keys: `sizeConstant` or `sizeSequence`; `opacityConstant` or `opacitySequence`; `colorConstant` or `colorSequence`; `accelerationVector` authored Z-up; `drag` seconds to halve velocity; `rotationRange`, `rotationSpeedRange`; `orientation` 0 camera billboard / 1 upright billboard (up locked world-up, still turns to face the camera - trees, flames) / 2 velocity-parallel / 3 velocity-perpendicular / 4 flat on the ground (ripples, rings, ground decals); `additiveBlend`, `isHidden`, `softParticleFadeDisabled`, `softParticleFadeDistance`, `squashConstant` or `squashSequence` (positive stretches up, negative sideways); `lit` 1 = sun+sky shading (smoke/dust/debris read as lit volumes; leave 0 for emissive fire/glow).
106
117
  - Resolution key: components render at full (native) resolution by default (crisp edges, no distance shimmer). Set `"renderResolution": "half"` on soft/volumetric high-overdraw components (smoke, fog, glow) — cuts GPU fill cost on weak GPUs; avoid on hard-edged/crisp content (`"full"` is accepted as an explicit no-op).
107
118
  - Ribbon/trail components (boost trails, sword arcs, tracers, dash streaks): `"type": "ribbon"` renders one connected camera-facing strip through the emitter's recent world path instead of billboards; layer with billboard components in the same effect (ideal boost = ribbon streak + sparse additive orbs). Keys: `trailSeconds` (point max age = trail length, default 0.5), `pointsPerSecond` (path sampling rate, default 30), `ribbonUv` = `"stretch"` (default) | `"repeat_per_meter"` (tiles by real distance), `ribbonPoints` (ring cap; defaults to fit trailSeconds*pointsPerSecond, max 256); `sizeConstant`/`sizeSequence` = strip WIDTH in meters over point age; texture/flipbook, color/opacity, `additiveBlend`, `renderResolution`, `boundingBoxSize`, `emitterPosition`/`emitterRotation`, soft-fade keys keep their exact meaning. Emission/shape/motion keys (`spawnRate`, `lifeTimeRange`, `emitterShapeType`, `speedRange`, `spreadAngle`, `drag`, `orientation`, `duration`, ...) are load ERRORS on a ribbon component. Ribbon effects are always continuous; the strip exists only while the emitter MOVES (attach to a moving object; `preview_effect` sweeps the emitter for you). Object `size` scales strip width — trail length is temporal (`trailSeconds` x actual speed). A trail that looks unchanged after resizing its object wants `trailSeconds`/`sizeSequence` edits, not more `size`.
108
119
  - Gun tracers / hitscan beams / lightning bolts: spawn ONE thin stretched mesh (or ribbon effect) per shot from muzzle to impact - LookAt the impact, scale the length axis to the distance, despawn in 0.05-0.15s; never per-frame display-cube chains. Bolt lifetime must be SHORTER than the refire interval or successive bolts merge into a solid rope; lightning = the same bolt with a few midpoints offset sideways per spawn + a ribbon afterglow.
@@ -121,7 +132,7 @@ out.alpha = smoothstep(0.48, 0.18, d);
121
132
  #end
122
133
  #particles SmallSmoke
123
134
  [
124
- { "particleTextureName": "texture.SmokePuff.color", "boundingBoxSize": [1, 1, 2], "renderResolution": "half", "spawnRate": 18, "duration": 0.8, "lifeTimeRange": [0.8, 1.4], "emitterShapeType": "sphere", "shapeStyle": "volume", "shapeInOut": "out", "radius": 0.2, "speedRange": [0.3, 0.8], "spreadAngle": [25, 35], "sizeSequence": [[0, 0.25], [1, 1.2]], "opacitySequence": [[0, 0.0], [0.15, 0.5], [1, 0.0]], "colorConstant": [0.65, 0.62, 0.55], "accelerationVector": [0, 0, 0.8], "drag": 0.7 }
135
+ { "particleTextureName": "texture.SmokePuff.color", "boundingBoxSize": [1, 1, 2], "boundingBoxCenter": [0, 0, 0.9], "renderResolution": "half", "spawnRate": 18, "duration": 0.8, "lifeTimeRange": [0.8, 1.4], "emitterShapeType": "sphere", "shapeStyle": "volume", "shapeInOut": "out", "radius": 0.2, "speedRange": [0.3, 0.8], "spreadAngle": [25, 35], "sizeSequence": [[0, 0.25], [1, 1.2]], "opacitySequence": [[0, 0.0], [0.15, 0.5], [1, 0.0]], "colorConstant": [0.65, 0.62, 0.55], "accelerationVector": [0, 0, 0.8], "drag": 0.7 }
125
136
  ]
126
137
  #end
127
138
  effect ChimneySmoke {
@@ -147,10 +158,11 @@ out = normalize(biquad(lp, sweep, cutoff = 2200, q = 1.2) * env_adsr(0.005, 0.1,
147
158
  <mesh_dsl_usage>
148
159
  - `#mesh <Name> ... #end` defines a local CPU-baked procedural static mesh; reference it as `model = "mesh.Name"` in scene declarations or `Ori.model.mesh.Name` in Weave code (SpawnObject, CreateStaticInstanceBatch). Authoring units are simulation centimeters, Z-up; baked meshes are bottom-center anchored at object origin and their deterministic cm AABB supplies box-collider bounds.
149
160
  - `#mesh <Name> destructible: masonry|wood|rock|concrete` bakes a hidden debris chunk set for the model; `obj.ShatterAndDestroy()` then bursts the object into tumbling chunks that bounce, settle, and fade (display-only, no sim collision; without the tag it acts as plain Destroy()). Preview with the preview_destruction tool; destructible mesh names cap at 46 chars.
150
- - For standard props (tree, crate, market_stall, ...), PREFER a bare `alias model.<noun>` (see <asset_aliases>): it binds a matching generated mesh from the library at load, or background-generates one.
161
+ - For standard props (crate, barrel, market_stall, ...) AND stock structure pieces the library has as words (wall, tower, watchtower, gate, fence, bridge, stair, shipping_container, ...), PREFER a bare `alias model.<noun>` (see <asset_aliases>): it binds a matching generated mesh from the library at load, or background-generates one; instance, rotate and `height=`-scale them into layouts. Trees are the exception: prefer a `#tree` species (hero/single) or `#forest` region (woods) - wind sway, LODs, slim per-trunk collision (see <tree_dsl_usage>); `alias model.tree` is a static low-poly prop that box-collides its whole canopy.
162
+ - `#mesh` is THE tool for bespoke level structure the play depends on - arena shells, ramps and multi-level platforms at exact sizes, buildings/facades, arches, custom cover - and for any custom shape or exact part control. `mesh_library_search` first (inline/adapt an existing piece), hand-author when nothing fits; walkable ramps/arches need `collision = mesh` (default collider = full AABB); `BuildRoom` (<room_building>) covers plain rectangular rooms.
151
163
  - Authoring or editing a `#mesh` body: read the `mesh_dsl` skill first — full language + builtin reference and the preview workflow. Do not guess builtin names or signatures from memory; unknown, duplicate, or wrong-type arguments fail loudly.
152
164
  - Lights on meshes: an `emitter("Bulb", at:, color:, intensity:, kind: "point"|"spot", ...)` line in the mesh body bakes a light socket; every placed instance shines from it (lamps, signs, projectors) - no separate light object needed. Syntax in the `mesh_dsl` skill; per-object toggle `Object.SetEmitterLightsEnabled(false)`.
153
- - Effects on meshes: `particles("<effect word>", at: (x,y,z)[, axis:, scale:])` in the mesh body embeds a particle effect the way `emitter()` embeds a light - mesh-local, riding every instance/rotation/saved object. The word must be a quoted literal and resolves like `effect.<word>` (local `#particles` block name, case-sensitive, or lowercase library word). Rest-pose like emitter(); prefer loops (fire, smoke, drips). For prop-attached fire/smoke prefer this over hand-placed effect objects.
165
+ - Effects on meshes: `particles("<ref>", at: (x,y,z)[, axis:, scale:])` in the mesh body embeds a particle effect the way `emitter()` embeds a light - mesh-local, riding every instance/rotation/saved object. The quoted ref is a local `#particles` block name (case-sensitive) or a marketplace `Ori.effect.<creator>.<name>`; anything else fails validation. Rest-pose like emitter(); prefer loops (fire, smoke, drips). For prop-attached fire/smoke prefer this over hand-placed effect objects.
154
166
  - Material slots: `material: "Name"` on generators or `paint(g, material: "Name")`. A slot name resolves to world `material.Name` when it exists (pair slots with `#texture Name` for low-poly color-blocking). Precedence: object/batch material override > explicit submesh override > authored local slot material > intrinsic GLB PBR.
155
167
  - Canonical slot names auto-fill from the built-in material library when the world doesn't declare them (library meshes only use these; name list in <texture_dsl_usage>). Recolor one for every library mesh by declaring it, e.g. `material Wood { base = "WoodDetail" tint = "#8a2b1e" }`.
156
168
  - GlassTransparent is the real translucent glass (fresnel reflection + grime; casts no shadow) - only for panes with real geometry behind them (balustrades, canopies, display cases), NOT hollow building window walls (blend reveals the empty shell; use `interior_window_pbr`, next clause). Derive tints in one line - `material PoolGlass { base = "GlassTransparent" tint = "#3A7E8C" }` - and override `opacity = 0..1` (1 = opaque) / `glassRoughness = 0..1` (reflection blur) only to tune.
@@ -159,7 +171,7 @@ out = normalize(biquad(lp, sweep, cutoff = 2200, q = 1.2) * env_adsr(0.005, 0.1,
159
171
 
160
172
  <tree_dsl_usage>
161
173
  - `#tree Name ... #end` (top-level): procedural tree species `tree.Name` for scene objects (`model = "tree.Name"`). Deterministic: trunk + branches become the sim collider; the renderer adds detail, leaf cards, LODs, impostors, and wind sway automatically.
162
- - Body is integer-only `key: value` lines (all optional; defaults are a curated broadleaf oak, so `seed:` alone gives a good tree). Keys: seed, levels (1-4), children (1-16), rings (whorl only: 0-40 branch rings along the trunk, decoupled from segments; 0 = one per segment - reference firs want ~15-20; members auto-stagger vertically), fill (whorl only: 0-16 short branches between rings - dense conifers), segments (2-8), collider_levels (dense conifers should author 1 - collide with the trunk, not hundreds of branchlets), trunk_height (cm), trunk_radius (cm), length_pct, radius_pct, angle_deg (child pitch from the parent axis; >90 emits below horizontal - with up_bias_pct the branch swoops down then up, the fir/spruce shelf), angle_jitter_deg, up_bias_pct, curve_jitter_pct, trunk_lean_deg (whole-trunk tilt, default 2; the trunk-shape low band), trunk_sweep_pct (one C- or S-arc over the trunk height, default 10; the mid band), trunk_noise_pct (trunk wobble amplitude; 0 = dead-straight leader with curve_jitter_pct branches) + trunk_noise_scale_pct (its wavelength as % of trunk height, 8-100, default 35; small = ragged bark-beat, large = slow bends - the high band), trunk_gnarl_pct (0-100; display-only radial lobe noise on the thick lower trunk - buttress/character silhouette for hero close-ups; collider stays smooth), leaves (frond stations per end branch), leaf_size (cm; mean size of one leaf-cluster card - each card is a baked spray of many leaves, not a single leaf; default 150), leaf_base (cm; no leaf cards below this height - bare forks/scaffolds; leaf_base_fade cm band above it ramps density to full, default 120), leaf_shell_pct (0-100; blend toward smooth per-pixel crown-shell shading - 0 = flat per-card facing, 100 = crown shades as a smooth rounded volume; default 100), leaf_clump_pct (0-100; shifts that shell reference from the whole-crown center toward each level-1 branch clump's own center, so big canopies shade as separate puffy lobes instead of one ball; default 40), leaf_detail_pct (0-300; leafy normal-detail sparkle bumped from the frond texture itself; 0 = smooth shell only; default 200), leaf_ao_pct (0-200; canopy AO strength darkening cards near the trunk column and canopy bottom; 0 = off; default 40), leaf_curl_pct (0-300; how strongly leaf cards bow out of their plane for varied, non-flat silhouettes; 0 = flat cards; default 150), leaf_far_shade_pct (0-150; far-billboard brightness; billboards lose cast shadows so <100 darkens them to match the near tree; default 60), droop_pct (tips sag), length_taper_pct (shorter branches higher up; ~70 = conifer cone), root_flare_pct (wider trunk base), wind_pct (0-300; sway response to the global wind - trunk, branches and leaf cards; 0 = rigid, default 100).
174
+ - Body is integer-only `key: value` lines (all optional; defaults are a curated broadleaf oak, so `seed:` alone gives a good tree). Keys: seed, levels (1-4), children (1-16), rings (whorl only: 0-40 branch rings along the trunk, decoupled from segments; 0 = one per segment - reference firs want ~15-20; members auto-stagger vertically), fill (whorl only: 0-16 short branches between rings - dense conifers), segments (2-8), collider_levels (dense conifers should author 1 - collide with the trunk, not hundreds of branchlets), trunk_height (cm), trunk_radius (cm), length_pct, radius_pct, angle_deg (child pitch from the parent axis; >90 emits below horizontal - with up_bias_pct the branch swoops down then up, the fir/spruce shelf), angle_jitter_deg, up_bias_pct, curve_jitter_pct, trunk_lean_deg (whole-trunk tilt, default 2; the trunk-shape low band), trunk_sweep_pct (one C- or S-arc over the trunk height, default 10; the mid band), trunk_noise_pct (trunk wobble amplitude; 0 = dead-straight leader with curve_jitter_pct branches) + trunk_noise_scale_pct (its wavelength as % of trunk height, 8-100, default 35; small = ragged bark-beat, large = slow bends - the high band), trunk_gnarl_pct (0-100; display-only radial lobe noise on the thick lower trunk - buttress/character silhouette for hero close-ups; collider stays smooth), leaves (frond stations per end branch), leaf_size (cm; mean size of one leaf-cluster card - each card is a baked spray of many leaves, not a single leaf; default 150), leaf_base (cm; no leaf cards below this height - bare forks/scaffolds; leaf_base_fade cm band above it ramps density to full, default 120), leaf_shell_pct (0-100; blend toward smooth per-pixel crown-shell shading - 0 = flat per-card facing, 100 = crown shades as a smooth rounded volume; default 100), leaf_clump_pct (0-100; shifts that shell reference from the whole-crown center toward each level-1 branch clump's own center, so big canopies shade as separate puffy lobes instead of one ball; default 40), leaf_detail_pct (0-300; leafy normal-detail sparkle bumped from the frond texture itself; 0 = smooth shell only; default 200), leaf_ao_pct (0-200; canopy AO strength darkening cards near the trunk column and canopy bottom; 0 = off; default 40), leaf_curl_pct (0-300; how strongly leaf cards bow out of their plane for varied, non-flat silhouettes; 0 = flat cards; default 150), leaf_far_shade_pct (0-150; far-billboard brightness; billboards lose cast shadows so <100 darkens them to match the near tree; default 60), far_fullness_pct (0-100; fraction of leaf cards baked into the far billboard - lower = sparser far crown; default 100), far_edge_pct (0-200; far-billboard silhouette cutoff - <100 fuller/softer crown edge, >100 tighter; default 100), far_trunk_pct (0-100; trunk/branch scaffold visibility in the far billboard, 0 = foliage only; default 100; preview far vs near live with the /treelod chat command), droop_pct (tips sag), length_taper_pct (shorter branches higher up; ~70 = conifer cone), root_flare_pct (wider trunk base), wind_pct (0-300; sway response to the global wind - trunk, branches and leaf cards; 0 = rigid, default 100).
163
175
  - Word keys: `mode: spiral|whorl|colonize` (whorl = rings of branches off a straight central leader - the pine/fir/spruce mode; colonize = space-colonization crown grown to fill a shape), `crown_shape: round|cone|vase` (colonize only), `leaf_mode: broadleaf|needle|weeping|mat|frond` (foliage style; weeping = hanging strand curtain, pair with high droop_pct for willows; frond = one whole-branch ribbon card per branch replacing the branch wood - THE fir/spruce mode, pair with mode: whorl and angle_deg ~95-115; mat = scattered flat shelf mats, a lighter conifer fill).
164
176
  - Colonize-only integer keys: points (crown attraction point count; auto-scales with crown size up to 1024, override for extra fine/coarse branching), leader (0-100: how far up the crown the central trunk persists before dissolving into forks - 0 = open broadleaf habit, oaks/beeches fork early; ~50-70 = beech/maple with a trunk partway into the crown; 100 = excurrent conifer/poplar spire), crown_width (cm, 0 = auto), crown_base (crown bottom height cm, 0 = auto; whorl mode also honors it as the lowest branch ring - ground-sweeping firs author ~150), crown_follow_pct (0-100, default 75: how much the crown envelope rides a leaning/swept trunk's top - 100 = crown centered on the bole, 0 = crown stays over the root and limbs reach back, the "leaning toward a clearing" habit).
165
177
  - Colonize authored crown shapes: `crown_sphere: x, y, z, r` / `crown_ellipsoid: x, y, z, rx, ry, rz` / `crown_dome: x, y, z_cut, rx, ry, rise` lines (cm, up to 8) grow branches to fill the union of those volumes, overriding crown_shape/crown_width/crown_base. A dome is the upper half of an ellipsoid with a flat cut at z_cut (umbrella/mushroom caps). Keep volumes near/above the trunk; disconnected lobes get bridged by a reaching branch. Colonize branches stay inside their envelope, so droop_pct barely bends them - weeping/cascading silhouettes (willows) need mode: spiral with high droop_pct instead.
@@ -174,7 +186,7 @@ Species recipes: see <example_tree_species> in <world_description_examples>.
174
186
  <forest_dsl_usage>
175
187
  - `#forest Name ... #end` (top-level): scatters a `#tree` species over an object's box region (`model = "forest.Name"`; set the object's `size` (cm) to shape the region, default 20x20x10 m; the region object is bottom-anchored like any object - z-span = [position.z, position.z + size.z]). Each tree stands on the terrain surface under its XY; where that surface lies outside the region's z-span the tree is skipped, so the z-span doubles as an elevation band (e.g. bottom above the waterline = no beach/underwater trees, top below the peaks = a tree line). An XY with no terrain under it is skipped too when the world has terrain (a region overhanging the terrain edge grows no drowned/floating trees); only fully terrain-less worlds anchor on the box bottom (flat-ground worlds just work; deliberate floating platforms use on_terrain: 0). Placements stay stable when the region is resized/moved. THE way to put woods on terrain - one region object, no spawn loops.
176
188
  - Body is `key: value` lines. `species: <TreeName>` (required, a `#tree` in this world); optional integers: seed, spacing (cm cell size, >= 100, default 700), density_pct (0-100 chance per cell), scale_min_pct / scale_max_pct (per-tree size range, default 85-125), on_terrain (default 1; 0 = always stand on the box bottom, e.g. floating platform forests).
177
- - Terrain filters (the precise control; the z-span is the coarse bracket): height_min_cm / height_max_cm keep trees inside a world-z band at their terrain anchor (waterline, tree line); slope_max_pct (0-100 on the #terrain_splat slope01 scale: 50 = 45deg, 35 ~ 30deg) skips steeper ground (no cliff trees). Filters gate terrain-anchored trees only and require on_terrain: 1. Prefer one big region + filters over hand-fitted boxes.
189
+ - Terrain filters (the precise control; the z-span is the coarse bracket): height_min_cm / height_max_cm keep trees inside a world-z band at their terrain anchor (waterline, tree line); slope_max_pct (0-100 on the #terrain_splat slope01 scale: 50 = 45deg, 35 ~ 30deg) skips steeper ground (no cliff trees). Filters gate terrain-anchored trees only and require on_terrain: 1. Prefer one big region + filters over hand-fitted boxes; region boxes clamp like any object (500 m/axis - scatter 1 km) and hold at most 16384 trees, denser boxes thin uniformly to fit (validation reports the kept %), so tile regions for more area or density.
178
190
  - Collision is per trunk only (a vertical capsule from the species' trunk), never the region box - players walk between trees and brush through leaves.
179
191
  - Verify placements with the `forest_lint` tool (source:"pending" before submit): terrain height/slope stats to pick filters numerically, then per-region placed/filtered counts + a tree-dot relief image.
180
192
  Example:
@@ -189,7 +201,7 @@ object Woods1 { model = "forest.PineWoods" position = (0, 0, 500) size = (12000,
189
201
 
190
202
  <scatter_dsl_usage>
191
203
  - `#scatter Name ... #end` (top-level): scatters ANY model over an object's box region (`model = "scatter.Name"`), exactly like `#forest` placement-wise (same region object, terrain anchoring, z-span band, filters) but display-only: NO collision, plus per-instance color variation. Use for rocks, bushes, props, debris; use `#forest` when trees should collide.
192
- - Body: `model: <url>` (required; `mesh.<Name>`, `Ori.model.*`, or a market model url) + the `#forest` keys minus `species`; optional integers hue_jitter_deg (0-180), sat_jitter_pct / val_jitter_pct (0-100) tint each instance (multiplies the region object's color; a tint on textured models, not true hue rotation).
204
+ - Body: `model: <url>` (required; `mesh.<Name>`, `Ori.model.*`, or a market model url) + the `#forest` keys minus `species`; optional integers hue_jitter_deg (0-180), sat_jitter_pct / val_jitter_pct (0-100) tint each instance (multiplies the region object's color; a tint on textured models, not true hue rotation); splat_mask_min_pct / splat_mask_max_pct keep instances only where the `user` mask (SetTerrainSplatMask) under them is inside that percent band (max 30 = off painted roads; min 60 = only on painted ground).
193
205
  Example:
194
206
  #scatter Boulders
195
207
  model: "mesh.Rock"
@@ -202,6 +214,19 @@ Example:
202
214
  object RockField { model = "scatter.Boulders" position = (0, 0, 500) size = (12000, 8000, 1000) }
203
215
  </scatter_dsl_usage>
204
216
 
217
+ <placement_usage>
218
+ - `#placement Name ... #end` (top-level): place ONE model N times on a regular grid - the architectural-rhythm tool (window frames across a facade, balconies, fence posts, lamp rows). Expands at load into a group + N real display instances (display-only, no collision; the renderer instances them; the vert budget counts every copy). The block is the family's ONLY serialization: patch the block to retune all instances at once - never write per-instance clauses.
219
+ - Body lines, in order: `let pts = grid(count: (nx, ny, nz), step: (x, y, z))` (count product <= 1024); optional `pts = skip(pts, [(i, j, k), ...])` removing 1-based cells (doors, storefront gaps); `place(model: "<url>", points: pts, position: (x, y, z), rotation: (x, y, z), size: (x, y, z))` - position is cell (1,1,1), rotation/size optional and shared by every instance. `//` comments ok. Instance names are `Name_i_j_k` (1-based).
220
+ - Edit ledger (machine-written, after the pipeline): when the USER hand-edits instances in the editor, saving writes `override Name_i_j_k { position = (x, y, z) rotation = (...) size = (...) }` / `remove Name_i_j_k` lines into the block (a family move retunes place() position instead). These record the user's own corrections: never write, retune, or drop ledger lines - patches touching them are rejected. Read them before rewriting the pipeline: a rewrite that leaves entries binding no cell keeps them as inert lines and the patch result names the orphaned edits - fold that intent into the new layout or confirm with the user.
221
+ - Random fields stay `#scatter`; thousands-scale non-editable decor stays CreateStaticInstanceBatch.
222
+ Example:
223
+ #placement AveWins
224
+ let pts = grid(count: (9, 1, 5), step: (378, 0, 334))
225
+ pts = skip(pts, [(5, 1, 1)])
226
+ place(model: "model.@arthit.windowframeblackny", points: pts, position: (9889, -18186, 744), rotation: (0, 0, -90))
227
+ #end
228
+ </placement_usage>
229
+
205
230
  <room_building>
206
231
  - For simple rectangular rooms, prefer `BuildRoom(Center, RoomSize, Thickness, Height, GateSide, GateWidth)` instead of hand-placing wall boxes. It creates clean aligned walls and avoids crooked wall edges/gaps.
207
232
  - `Center` is the room center position. `RoomSize` is the INNER clear size in cm. Walls sit just outside that footprint, with bottoms at `Center.Z` and height `Height`.
@@ -221,35 +246,40 @@ event OnSpawned {
221
246
  <tilemaps>
222
247
  - Use `tileSet` + `tileMap` for grid-authored modular meshes:
223
248
  `tileSet Walls { "W" = "Ori.model.creator.wall" solid 300 autotile }`
224
- `tileMap House { tileSet = "Walls" cellSize = 300 tiles = """WWW\nW W\nWWW""" }`
249
+ `tileMap House { tileSet = "Walls" tileSize = 300 tiles = """WWW\nW W\nWWW""" }`
225
250
  - `tiles` is an ASCII grid: space = empty cell; every row must have the same width; first text row is highest +Y.
226
- - `cellSize` is cell spacing and visual tile scale in cm. `cellSize = 300` makes normal 100cm-authored tile meshes render 3x and places centers 300cm apart.
251
+ - `tileSize` is tile spacing and visual tile scale in cm. `tileSize = 300` makes normal 100cm-authored tile meshes render 3x and places centers 300cm apart.
227
252
  - Tile visuals are centered in cell X/Y but pivot at tileMap Z=0: each model's bounding-box bottom sits on the tileMap plane, even when variants have different heights.
228
- - `solid [height_cm]` is collider height only; omit `solid` for decorative/walkable tiles. `solid` without height uses `cellSize` as collider height.
253
+ - `solid [height_cm]` is collider height only; omit `solid` for decorative/walkable tiles. `solid` without height uses `tileSize` as collider height.
229
254
  - `autotile` derives variant model urls from the base/stem: `_isolated`, `_end`, `_straight`, `_corner`, `_3way`, `_4way`. The unsuffixed base may be only a naming stem and does not need to exist as an asset.
230
255
  - Autotile connects only same tile characters/palette entries. Yaw-0 model convention in sim XY (+Z up): `_end` cap/dead-end faces `+X` so its one connection is `-X`; `straight {-X,+X}`, `corner {+X,+Y}`, `3way {-X,+Y,+X}` open `-Y`, `4way {-X,+Y,+X,-Y}`. Rotations are right-hand about +Z; clockwise quarter-turns use Z -90.
231
256
  </tilemaps>
232
257
 
233
258
  <tile_pathfinding>
234
- - Use TileGrid helpers for cheap walkable/not-walkable A* on tile coordinates. TileIds in returned paths are opaque Numbers; never decode them with `%` or `/`. Use `TileGridPathTile(Grid, TileId, OriginTile)` or `TileGridPathWorld(Grid, TileId, OriginTile)`.
235
- - `TileGridCreate(TilesX, TilesY, DefaultWalkable)` returns an opaque compact List. `TileGridCreate(TilesX, TilesY, DefaultWalkable, EdgeBlockingEnabled)` enables optional edge-wall storage when the Bool is true. Do not call generic mutating List functions (`Add`, `Set`, `Resize`, `Clear`, `Pop`, etc.) on it; use TileGrid helpers. Padded grid capacity is capped at 1,048,576 tiles.
259
+ - Use TileGrid helpers for cheap walkable/not-walkable A* on tile coordinates. Every TileGrid call speaks WORLD TILES (`WorldToTile(pos)` output feeds straight in): the grid stores its OriginTile from creation and converts internally - no offset math at call sites, ever. TileIds in returned paths are opaque Numbers; never decode them with `%` or `/`. Use `TileGridPathTile(Grid, TileId)` (world tiles out) or `TileGridPathWorld(Grid, TileId)` (world cm out).
260
+ - `TileGridCreate(TilesX, TilesY, OriginTile, DefaultWalkable)` returns an opaque compact List covering world tiles OriginTile.xy .. OriginTile.xy+TilesX/Y-1 (center a world-spanning grid with a negative origin, e.g. `Vector(-1024, -1024, 0)`). `TileGridCreate(TilesX, TilesY, OriginTile, DefaultWalkable, EdgeBlockingEnabled)` enables optional edge-wall storage when the Bool is true. Do not call generic mutating List functions (`Add`, `Set`, `Resize`, `Clear`, `Pop`, etc.) on it; use TileGrid helpers. Padded grid capacity is capped at 1,048,576 tiles.
261
+ - `TileGridSetZone/SetZoneRect/ZoneAt` keep a per-tile zone byte for game data (districts, territory, resource layers, cliff tiers; no pathing effect) on any 2D grid - dense grids grow the plane on the first SetZone; layered 3D grids reject.
262
+ - `TileGridCreateFromTerrain(TilesX, TilesY, OriginTile, MaxSlope01, MaxStepCm)` = a world-sized edge-blocking grid (up to 8192 per axis) whose walkability comes from the terrain (slope, water, cliff steps as blocked edges) without storing tiles: only the 32x32-tile chunks you stamp or edit are stored. Same calls as other grids; `TileGridSetWalkable(Grid, X, Y, true)` forces a tile open (bridges, docks), false stamps it. FindPath/LOS/SmoothPath serve trips up to 256 tiles apart (farther = runtime error); for any distance `TileGridFindRoute(Grid, StartX, StartY, GoalX, GoalY)` returns a route List (empty = unreachable) that only `StartTilePathFollowStepped` accepts - the mover paths leg by leg as it walks, re-routes when a coming leg is walled (a leg already underway is walked) and at a dead end halts into `TilePathFollowTakeBlocked()`. `TileGridIsReachable` has no distance limit (true is constant-time; false re-searches every call - cache false verdicts, re-check after edits). `TileGridRegionAt(Grid, X, Y)` = the tile's region id (0 = unwalkable); equal ids = reachable from each other (filter targets by id before pathing); ids link only through explored chunks (stamped, edited or searched), so a different id means ask IsReachable, not unreachable. After terrain edits, drain `TerrainOverlayTakeChangedRects()` and re-bake just that ground with `TileGridRefreshFromTerrainRect(Grid, X1, Y1, X2, Y2)` (the rect args ARE the drained world-cm quads, fed directly - +-1-tile halo included; ONE call re-derives both walkable and edges under the grid's stored params; a dig under already-explored ground stays stale until you do). FlowFieldBuild and BakeCrowdObstacles are not available on these grids.
236
263
  - Edge-blocking grids can use `TileGridSetEdgeBlocked(Grid, X1, Y1, X2, Y2, Blocked)` for cardinal-adjacent tile pairs. Compact grids reject SetEdgeBlocked; `TileGridIsEdgeBlocked(...)` returns false for valid compact-grid edges. Edge-enabled diagonal pathfinding does not cross blocked cardinal edges, and a diagonal step requires all four cardinal edges around the crossed 2x2 tile corner to be clear.
237
264
  - `BuildTileBlockedGridFromTag(OriginTile, TilesX, TilesY, BlockerTag)` creates a walkable grid and blocks tiles whose world-center point overlaps an object with the tag - sampled on OriginTile.z's plane only, so blockers floating above/below it are missed (0 tiles blocked while tagged blockers exist = runtime error naming their z span). Volumetric blocking: TileGridSetWalkableFromStaticColliders (ZMin/ZMax) or TileGridStampObject. Build/rebuild grids when blockers change, not every frame.
238
- - `TileGridSetWalkableFromStaticColliders(Grid, OriginTile, ZMin, ZMax)` re-rasterizes an existing grid from the static colliders: every tile resets, and a tile blocks when any static collider's world AABB overlaps its rect within the [ZMin, ZMax] cm band (rotated colliders use their conservative AABB; colliders covering the whole grid footprint - floors, world bounds - are ignored). Static world objects, static PhysicsObjects, and tilemap solid cells all block; keep ZMin above the walk surface AND above any solid ground tilemap's top (e.g. 20) so floor slabs don't block their tiles. Layered grids rasterize per layer ([ZMin, ZMax] relative to each layer plane). It RESETS every tile - call it BEFORE the FromTerrain pair; for runtime placements stamp instead of re-rastering.
239
- - `TileGridSetWalkableFromTerrain(Grid, OriginTile, MaxSlope01)` marks tiles UNWALKABLE where the topmost terrain at the tile center is steeper than MaxSlope01 (slope01 = g/(1+g); 0.5 = 45deg; walkers wedge above ~0.5, so 0.4-0.45 is a safe unit ceiling) or absent. Subtractive only - never re-opens tiles.
240
- - `TileGridSetEdgesFromTerrain(Grid, OriginTile, MaxStepCm)` (edge-blocking grids) blocks the edge between cardinal neighbors whose terrain height difference exceeds MaxStepCm: cliffs become impassable EDGES while rim tiles stay standable; graded ramps (small per-tile rise) stay open. Tile centers sample mid-cell, so a hard TierCm cliff reads as two TierCm/2 steps - MaxStepCm = TierCm/3 separates cliff jumps from ramp rise; size ramps >= 2 tiles of run per tier step so their slope stays below the ~45deg walk limit.
241
- - Cliff-map load order (manager OnSpawned): collider raster -> FromTerrain -> EdgesFromTerrain -> BakeCrowdObstacles -> IsReachable asserts. Then placements only stamp.
242
- - `TileGridStampRect(Grid, X1, Y1, X2, Y2, Blocked)` / `TileGridStampObject(Grid, OriginTile, Obj, Blocked)` stamp footprints incrementally (rect corners clamp to the grid; spawn: true, despawn: false re-opens the stamped rect - overlapping footprints must re-stamp their survivors) - cheaper than re-rastering after each placement.
265
+ - `TileGridSetWalkableFromStaticColliders(Grid, ZMin, ZMax)` re-rasterizes an existing grid from the static colliders: every tile resets, and a tile blocks when any static collider's world AABB overlaps its rect within the [ZMin, ZMax] cm band (rotated colliders use their conservative AABB; colliders covering the whole grid footprint - floors, world bounds - are ignored). Static world objects, static PhysicsObjects, and tilemap solid cells all block; keep ZMin above the walk surface AND above any solid ground tilemap's top (e.g. 20) so floor slabs don't block their tiles. Layered grids rasterize per layer ([ZMin, ZMax] relative to each layer plane). It RESETS every tile - call it BEFORE the FromTerrain pair; for runtime placements stamp instead of re-rastering.
266
+ - `TileGridSetWalkableFromTerrain(Grid, MaxSlope01)` marks tiles UNWALKABLE where the topmost terrain at the tile center is steeper than MaxSlope01 (slope01 = g/(1+g); 0.5 = 45deg; walkers wedge above ~0.5, so 0.4-0.45 is a safe unit ceiling) or absent. Subtractive only - never re-opens tiles.
267
+ - `TileGridSetEdgesFromTerrain(Grid, MaxStepCm)` (edge-blocking grids) blocks the edge between cardinal neighbors whose terrain height difference exceeds MaxStepCm: cliffs become impassable EDGES while rim tiles stay standable; graded ramps (small per-tile rise) stay open. Tile centers sample mid-cell, so a hard TierCm cliff reads as two TierCm/2 steps - MaxStepCm = TierCm/3 separates cliff jumps from ramp rise; size ramps >= 2 tiles of run per tier step so their slope stays below the ~45deg walk limit.
268
+ - Dense-grid (`TileGridCreate`) cliff-map load order (manager OnSpawned): SetWalkableFromStaticColliders -> SetWalkableFromTerrain -> SetEdgesFromTerrain -> BakeCrowdObstacles -> IsReachable asserts. Then placements only stamp. `TileGridCreateFromTerrain` grids skip the whole chain (terrain already IS their walkability; placed buildings already repel crowds, and the bake is rejected there).
269
+ - `TileGridStampRect(Grid, X1, Y1, X2, Y2, Blocked)` / `TileGridStampObject(Grid, Obj, Blocked)` stamp footprints incrementally (rect corners clamp to the grid; spawn: true, despawn: false re-opens the stamped rect - overlapping footprints must re-stamp their survivors) - cheaper than re-rastering after each placement.
243
270
  - `TileGridIsReachable(Grid, X1, Y1, X2, Y2)` (layered: 7-arg form) = "does an 8-dir path exist" without building one - assert connectivity (spawn points, ramps) in a manager OnSpawned with `RaiseWorldError` so broken maps fail at load, not mid-game; also check before wall placements that could seal a region.
244
- - `TileGridBakeCrowdObstacles(Grid, OriginTile)` bakes avoidance line obstacles from the grid's blocked edges and unwalkable borders so crowd units are never pushed off cliffs or into tilemap walls avoidance cannot see. It is a snapshot: re-bake after runtime walkability edits (opened gates, razed bridges); placed buildings already repel via their own colliders, no re-bake needed.
271
+ - `TileGridDebugOverlay(Grid, true)` paints a FromTerrain grid onto the terrain for debugging: walkable/blocked tint, cost + zone marks and stored-chunk shading in a window around the camera, blocked edges as bars. Synced like other debug toggles - turn it off (false) before shipping.
272
+ - `TileGridBakeCrowdObstacles(Grid)` bakes avoidance line obstacles from the grid's blocked edges and unwalkable borders so crowd units are never pushed off cliffs or into tilemap walls avoidance cannot see. It is a snapshot: re-bake after runtime walkability edits (opened gates, razed bridges); placed buildings already repel via their own colliders, no re-bake needed.
245
273
  - One grid, many movers (RTS): build the grid ONCE on a manager scene object (`var Grid: List<Number>` field; `TileGridCreate` + `TileGridSetWalkableFromStaticColliders` in its OnSpawned) and let every unit reach it through a scene cast - `TileGridFindPath8((Scene.Manager as HQ).Grid, ...)`. Cross-class field access works for List fields; never rebuild the grid per unit.
246
- - `TileGridFindPath4/8(Grid, StartX, StartY, GoalX, GoalY[, GoalToleranceTiles])` returns a reversed path List: Goal ... Start. Empty means blocked start/goal, out of bounds, or no path (a TRUE verdict - the search budget covers the whole grid, so a no-path query floods the goal's component; on big maps gate mass orders with one TileGridIsReachable, share flow fields, and cache no-path results). GoalToleranceTiles (0-16) accepts the cheapest walkable tile within that ring of the goal - set it when ordering onto colliders (buildings, resource nodes); empty then means the ring itself is unreachable, and out-of-grid Start/Goal clamp to the nearest edge tile (a chase target that walked off the grid paths to the boundary instead of erroring). Compact TileGridFindPath8 preserves corner cutting through unwalkable side tiles; edge-enabled grids still require crossed cardinal edges to be clear.
274
+ - `TileGridFindPath4/8(Grid, StartX, StartY, GoalX, GoalY)` returns a reversed path List: Goal ... Start. Empty means blocked start/goal or no path (a TRUE verdict - the search budget covers the whole grid, so a no-path query floods the goal's component; on big maps gate mass orders with one TileGridIsReachable, share flow fields, and cache no-path results); out-of-grid Start/Goal clamp to the nearest edge tile (a chase target that walked off the grid paths to the boundary instead of erroring). The `NextTo` forms (same args) accept a goal you cannot stand on (building, resource node, rock): the path ends on the best adjacent tile; a walkable goal still paths exactly. Compact TileGridFindPath8 preserves corner cutting through unwalkable side tiles; edge-enabled grids still require crossed cardinal edges to be clear.
247
275
  - `TileGridHasLineOfSight(Grid, StartX, StartY, GoalX, GoalY)` (layered: 7-arg form, same Z only) returns true when the straight tile-center corridor is fully walkable (crossed edges clear on edge grids; exact corner crossings also need both side tiles). Chasing a moving target: LOS true -> `MoveTowardContinuous` at the live position, false -> FindPath + follow.
248
276
  - `TileGridSmoothPath(Grid, Path)` returns a new reversed path with waypoints dropped wherever the straight corridor between the survivors is clear (string pulling; never smooths across a layer change). Walkers zigzagging tile centers -> smooth once after FindPath, before following.
277
+ - Per-tile cost: `TileGridCreate(TilesX, TilesY, DefaultWalkable, EdgeBlockingEnabled, DefaultCost)` + `TileGridSetCost(Grid, X, Y, Cost)` / `TileGridSetCostRect(Grid, X1, Y1, X2, Y2, Cost)` / `TileGridGetCost(Grid, X, Y)`. Cost = percent of normal (100 normal, 67 road-like, 200 mud; 1..255); FindPath and FlowField prefer cheap tiles. SmoothPath rejects cost grids (it would straighten through roads); layered 3D grids have no cost plane. FromTerrain grids grow the plane on the first SetCost (no create arg); there FindRoute prefers cheap chunks and followers also SLOW/SPEED by tile cost (dense grids: scale the Speed you pass by GetCost yourself).
249
278
  - To follow a path, call `MoveAlongTilePathContinuous(Path, Grid, OriginTile, Speed[, ArriveWithinCm[, OrientTurnRate]])` each frame: one call steps the mover, pops reached waypoints, and returns true once the path is consumed (false for an already-empty path). ArriveWithinCm (default 1) completes the FINAL waypoint within that distance - set it when the goal can be occupied (resource node, building) so contact does not stall arrival. OrientTurnRate faces the mover along its travel (deg/s; 0 = instant) - RTS vehicles read wrong without it. Prefer it over manual MoveToward + Pop loops; it is one sim call per mover per frame.
250
279
  - Mover frozen after an early order switch: a half-consumed path re-followed later pushes at a stale waypoint - Clear() the path list whenever you leave a follow before it returns true.
251
- - For CROWDS (Weave2, hundreds+ of movers): `obj.StartTilePathFollow(Path, Grid, OriginTile, Speed)` registers the mover with the native segment follower (same movement semantics, zero per-mover calls while walking; returns false for an empty path). Start CONSUMES the path list (it is empty afterwards - the follower owns a copy); sim-visible mover positions update at waypoint granularity while walking. One manager then drains `TilePathFollowTakeArrivals()` once per frame (returns finished movers as `List<Object>`) to run arrival logic. `obj.StopTilePathFollow()` cancels; `obj.IsTilePathFollowDone()` polls instead of draining (an arrival stays set until taken/stopped/restarted). Starting again replaces the previous follow.
252
- - For MANY movers sharing ONE destination set (group move / rally / harvest points): `TileGridFlowFieldBuild(Grid, GoalTiles)` bakes per-tile direction toward the nearest goal into a reusable opaque List (GoalTiles = flat X,Y,Z coordinate triples; unwalkable goal tiles are skipped; every goal blocked -> all-unreachable field). `obj.StartTileFlowFollow(Field, Grid, OriginTile, Speed)` walks it with the same native follower - the lists stay CALLER-OWNED, one field serves any number of movers; returns false when the mover's tile is off-grid or unreachable in the field. Fields are build-once snapshots: after `TileGridSetWalkable` edits affected movers HALT and surface in `TilePathFollowTakeBlocked()` (drains like TakeArrivals) - rebuild the field and restart them. TakeArrivals/Stop/IsDone work as for path follows.
280
+ - For CROWDS (Weave2, hundreds+ of movers): `obj.StartTilePathFollowStepped(Path, Grid, Speed)` registers the mover with the native segment follower (same movement semantics, zero per-mover calls while walking; returns false for an empty path). Start CONSUMES the path list (it is empty afterwards - the follower owns a copy); sim-visible mover positions update at waypoint granularity while walking (the renderer glides the mesh between waypoints, but bars/effects/avoidance read the stepped sim position - swarm lane, not for commanded close-camera units). One manager then drains `TilePathFollowTakeArrivals()` once per frame (returns finished movers as `List<Object>`) to run arrival logic. `obj.StopTilePathFollow()` cancels; `obj.IsTilePathFollowDone()` polls instead of draining (an arrival stays set until taken/stopped/restarted). Starting again replaces the previous follow.
281
+ - For MANY movers sharing ONE destination set (group move / rally / harvest points): `TileGridFlowFieldBuild(Grid, GoalTiles)` bakes per-tile direction toward the nearest goal into a reusable opaque List (GoalTiles = flat X,Y,Z coordinate triples; unwalkable goal tiles are skipped; every goal blocked -> all-unreachable field). `obj.StartTileFlowFollowStepped(Field, Grid, OriginTile, Speed)` walks it with the same native follower - the lists stay CALLER-OWNED, one field serves any number of movers; returns false when the mover's tile is off-grid or unreachable in the field. Fields are build-once snapshots: after `TileGridSetWalkable` edits affected movers HALT and surface in `TilePathFollowTakeBlocked()` (drains like TakeArrivals) - rebuild the field and restart them. TakeArrivals/Stop/IsDone work as for path follows.
282
+ - COMMANDED units on a shared field: call `obj.MoveAlongTileFlowContinuous(Field, Grid, OriginTile, Speed[, ArriveWithinCm[, OrientTurnRate]])` each frame - samples the field at the mover's tile and moves through the same per-frame write as MoveAlongTilePathContinuous (avoidance shapes it; sim position never steps; directions blend across tiles, so off-axis marches run straight). Returns true on reaching a goal tile (within ArriveWithinCm of its center, default 1); false while walking, and false WITHOUT motion when the mover's tile is off-grid or unreachable in the field. Crowd movers also finish by PACKING: blocked by a unit already finished on a field with the SAME goals, the mover parks touching it and returns true - a group order settles as one tight blob, no per-unit target spread needed. Parked units hold ground (others route around them) until their next movement call un-parks them - but they YIELD to same-team movers pushing through (easing aside for a moment, then re-freezing in place); rebuilding the field to the same goals keeps the pack growing, new goals start fresh. It re-reads the field every call, so a rebuilt field redirects walkers immediately - no halt/restart. A mover standing on a BLOCKED tile (crowd pressure against a footprint, a building stamped over it, spawn overlap) escape-steps toward the nearest walkable tile instead of freezing (still returns false), and a step that would LAND in a blocked tile slides along its walkable axis instead - movers round building corners rather than pressing them (both also apply to MoveAlongTilePathContinuous). 2D grids only (layered grids: use the Stepped follower).
253
283
  - Layered grids (bridges/multi-floor): `TileGridCreate3D(TilesX, TilesY, Layers, DefaultWalkable)`; each layer is an independent walkable plane at OriginTile.z + Z. `TileGridSetWalkable/TileGridIsWalkable(Grid, X, Y, Z, ...)`, `TileGridSetLinkUp(Grid, X, Y, Z, Linked)` marks a stair cell connecting Z and Z+1 (both ends must also be walkable), `TileGridFindPath4/8(Grid, StartX, StartY, StartZ, GoalX, GoalY, GoalZ)`. On layered grids MoveAlongTilePathContinuous drives Z too (object bottom rests on the layer plane). No edge blocking on layered grids.
254
284
  </tile_pathfinding>
255
285
 
@@ -284,12 +314,17 @@ event OnTuneChanged {
284
314
 
285
315
  <persistent_state_v1>
286
316
  - `@persist var Name = ...` (class-scope top-level var): value survives save/load and world reload
287
- - supported types: Number, Bool, Text, Vector, Position (others are a compile error)
317
+ - supported types: Number, Bool, Text, Vector, Position, List<Number|Bool|Text|Vector|Position>, List<struct> (structs without Object fields), Map<Number, Number|Bool|Text> (others are a compile error); container vars cap at 4096 entries - oversized vars drop loudly at save
318
+ - `@persistInventory var Inv: Object` (a CreateInventory handle var): contents persist by item name + counts + selected slot and the var holds the restored inventory on load; re-run BindInventory in OnSpawned for slot UIs
319
+ - TileGrids and their cost/zone planes never persist: rebuild the grid in OnSpawned and re-stamp from persisted state (entity Lists, recreated spawns)
320
+ - runtime tileMap paints (SetCell/SetCellsRect) persist automatically as engine diffs vs the authored map - no replay list needed; a diff over 8192 changed cells drops whole + loudly. If the authored tileMap is resized/repainted in the editor, saved paints drop loudly on the next load
321
+ - TerrainDig/TerrainFlatten overlays persist automatically with the save; very large overlays (>~120KB compressed of digs) drop loudly on load
288
322
  - scene objects rehydrate by object name; a standalone `@persistSpawned` line inside a class also recreates its runtime-spawned instances (saved template + position/rotation/size + tagged vars); crowd instances and multi-object templates are not recreated
289
323
  - class edits migrate loudly: removed/retyped tagged vars are dropped and reported; new tagged vars start at their defaults
290
324
  - a destroyed scene object reappears on load; persist a `@persist var Opened` style flag and react in OnSpawned
291
325
  - saving is automatic per project; there is nothing to call
292
326
  - PersistClear() wipes the project's saved state (pair with RestartGame() for a New Game button)
327
+ - saved state survives full code re-pastes: a game that persists must ship a New Game control, and KV data (no migration, unlike tagged vars) wants a schema-version key checked on spawn - wipe/migrate on mismatch
293
328
  - prefer @persist over PersistGet/SetNumber for object state; the KV remains for per-player counters
294
329
  </persistent_state_v1>
295
330
 
@@ -307,15 +342,17 @@ event OnTuneChanged {
307
342
  - Per-player UI: a scene `screenCanvas` is shared (shown to everyone).
308
343
  - For UI unique to one player: SpawnCanvas(SavedObject.Hud, pos) in the player OnSpawned, then hud.SetPlayerOwnerIndex(GetPlayerIndex()) -> renders and hit-tests only for that viewer. Use hud.SetPlayerOwnerIndex(-1) to make UI public again. Grab child elements via hud.FindChildUI("Name") (deep search; spawned-canvas children are not reachable as Scene.X).
309
344
  - A shared canvas still routes clicks per-player: ClickButton handlers that mutate per-player state must branch on Event.ButtonClicker.
310
- - Per-player world objects: keep one shared map/board, then use obj.SetRenderVisibleOnlyToPlayerIndex(GetPlayerIndex()) for private meshes/cards/markers. Use obj.SetRenderVisibleToAllPlayers() to make the world object public again; ownership metadata is not cleared.
345
+ - Per-player world objects: keep one shared map/board, then use obj.SetRenderVisibleOnlyToPlayerIndex(GetPlayerIndex()) for private meshes/cards/markers. Use obj.SetRenderVisibleToAllPlayers() to make the world object public again (clears only-to AND hidden-for); ownership metadata is not cleared.
346
+ - Inverse: obj.SetRenderHiddenForPlayerIndex(i) hides a shared object from exactly that player, visible to everyone else (e.g. hide the original wall from the player previewing its replacement).
347
+ - Per-player build ghosts: DrawPreviewForPlayer(GetPlayerIndex(), SavedObject.X, pos, rot) each frame — only that player's client renders it (DrawPreview draws on every client).
311
348
  - This is render/pick/mouse-target privacy, not secret sim state or per-player physics/audio. For private cards/markers, usually also call card.SetCanCollide(false).
312
- - SetRenderVisibleOnlyToPlayerIndex(i) is a RUNTIME ERROR if no live player has index i — call it from that player's own OnSpawned, never on a hardcoded index from world setup or forever loops.
349
+ - SetRenderVisibleOnlyToPlayerIndex(i)/SetRenderHiddenForPlayerIndex(i) are a RUNTIME ERROR if no live player has index i — call them from that player's own OnSpawned, never on a hardcoded index from world setup or forever loops.
313
350
  - There is NO per-player collision filter: two players' "private" objects at the same XYZ still collide physically. Separate per-player arenas/boards at different world coordinates; render privacy is cosmetic, not the separation mechanism.
314
351
  - Per-player game state (gold, board, score): NEVER a shared-manager var — that is one world value, not "mine"; sim code has no "local player". Use '#class player' vars (each PlayerObject has its own copy) or manager Lists keyed by player index.
315
352
  - Converting a single-player game to multiplayer: FIRST move shared-manager scalars (gold/lives/shop/board slots) into '#class player' vars or per-index Lists, THEN add per-player zones/owned UI. Do NOT clone P2_-prefixed copies of canvases/objects or mirror the map with coordinate offsets per player.
316
353
  - Validation/simulate auto-joins a second mock player a few frames in when these per-player APIs are referenced, so per-player join/visibility/index logic is actually exercised.
317
354
  - Players/teams:
318
- - SetPlayerOwnerIndex/GetPlayerOwnerIndex, SetRenderVisibleOnlyToPlayerIndex/SetRenderVisibleToAllPlayers, SetTeam/GetTeam
355
+ - SetPlayerOwnerIndex/GetPlayerOwnerIndex, SetRenderVisibleOnlyToPlayerIndex/SetRenderHiddenForPlayerIndex/SetRenderVisibleToAllPlayers, SetTeam/GetTeam
319
356
  - Scene field: `object X { team = 0 }` assigns the spawn team (same values as SetTeam; omit for teamless).
320
357
  - Fog of war: SetFogOfWarEnabled(true) (needs terrain) then SetSightRadius on teamed objects - per-team grid; shroud, explored-memory, enemy-object hiding, and pick-through are built in (no per-object SetRenderVisible needed).
321
358
  - Iterate via GetAllPlayers()/GetAllPlayersWithTag(..) (existing players only, no None entries).
@@ -350,6 +387,9 @@ event OnSpawned {
350
387
  #end
351
388
  </multiplayer_guidance>
352
389
 
390
+ <multi_stage_projects>
391
+ One project, several scenes: a single .weave source with optional `#shared ... #endshared` (classes prepended to every stage) followed by one `#stage <Name> ... #endstage` per scene (plain world text inside; comments / `#set` / `#project` lines may precede everything). SwitchStage("Name") swaps scenes at runtime without leaving the project or the multiplayer session; carry values with LoadWorldTransferClear+Push before the call; @persist state is per stage. A project source is not plain world text: load/validate it through the project lanes, which compose each stage (prelude + shared + stage body) into a plain world.
392
+ </multi_stage_projects>
353
393
 
354
394
  <quick_world_description_examples>
355
395
  #class Class0
@@ -475,8 +515,9 @@ event OnSpawned {
475
515
  - PhysicsObject: like WorldObject, but starts as dynamic/CanCollide physics
476
516
  - CrowdObject: Useful as creatures/walkable-NPCs. like WorldObject, but starts with crowd collision avoidance
477
517
  - it steers around other crowd agents and obstacles it approaches at an angle; a blocker dead-ahead STOPS it (avoidance is anti-collision, not pathfinding) - route around structures with tile pathfinding
478
- - RTS units: route with `MoveAlongTilePathContinuous` on a `crowd` receiver - the path sets the preferred velocity and avoidance shapes it every frame (global path + local separation). The zero-call `StartTilePathFollow` is waypoint-granular, so avoidance cannot shape it mid-segment - use it for overlap-tolerant swarms instead. Terrain cliffs and tilemap walls are invisible to avoidance until `TileGridBakeCrowdObstacles` bakes them from the nav grid.
518
+ - RTS units: route with `MoveAlongTilePathContinuous` (per-unit orders) or `MoveAlongTileFlowContinuous` (group orders on one shared field) on a `crowd` receiver - both set the preferred velocity per frame and avoidance shapes it (global route + local separation). The zero-call `StartTilePathFollowStepped`/`StartTileFlowFollowStepped` followers are waypoint-granular, so avoidance cannot shape them mid-segment - overlap-tolerant swarms only. Terrain cliffs and tilemap walls are invisible to avoidance until `TileGridBakeCrowdObstacles` bakes them from the nav grid (dense grids only). Path-walking city populations skip avoidance entirely: `walker` + the stepped follower.
479
519
  - Avoidance scope: XY-plane only; Z/height is ignored.
520
+ - Keep spawn/idle spacing >= 2x agent diameter - tighter packing interpenetrates at rest, avoidance degrades, and dense piles dominate sim cost. Typical agent radius (= half the x-size) 15-40cm.
480
521
  - Gravity is ON at spawn; crowd bodies stand on static geometry, terrain, and tilemaps (not on dynamic objects). ClimbingCrowdObject remains the climb/stack-capable crowd.
481
522
  - For grid-based or fixed-lane games (e.g., Tower Defense), avoid using CrowdObject, as its built-in avoidance can cause objects to move off their intended path.
482
523
  - Prefer it over plain WorldObject for free-roaming monster swarms in top-down games.
@@ -502,7 +543,7 @@ event OnSpawned {
502
543
  <variable_types>
503
544
  - Number — real (integers/decimals/scientific); arithmetic includes % and %= (modulus).
504
545
  - Bool — true/false.
505
- - Text — string; + concatenates and auto-converts Number/Position/Object operands (no .ToText() needed in concat; use it only for a standalone value, e.g. SetText(N.ToText())). Parsing: Text.Word(Index) (0-based whitespace-split word, "" past end), Text.WordCount(), Text.ToNumber() (0 if not numeric).
546
+ - Text — string; + concatenates and auto-converts Number/Position/Object operands (no .ToText() needed in concat; use it only for a standalone value, e.g. SetText(N.ToText())). Parsing: Text.Word(Index) (0-based whitespace-split word, "" past end), Text.WordCount(), Text.ToNumber() (0 if not numeric). .ToText() renders minimal form ("1", not "1.000").
506
547
  - Object — handle to a world/UI object; may be None
507
548
  - Timer — countdown handle; new timers start in Done state.
508
549
  - Vector — (X,Y,Z) direction/offset; also used for RGB triples.
@@ -511,8 +552,9 @@ event OnSpawned {
511
552
  - 'var X: List<T>' needs no initializer (class scope or local); lists start empty.
512
553
  - Bare 'var X: List' infers T from usage; annotate List<T> explicitly when inference reports conflicting evidence.
513
554
  - No List<List<T>>. Non-empty literals ([1,2,3]) are const-only: runtime lists start empty then Add, or Resize(n, default).
514
- - Class-scope 'const Ids = [10, 20, 30]' (Number/Bool/Text/Vector elements, type inferred) bakes a read-only table into the program: Get/Count/Contains/for-each work, writes are runtime errors. Const tables have zero per-frame snapshot/hash cost — item/monster/config databases belong here, not in runtime lists filled row-by-row at startup.
515
- - Map<Number, T> — keyed store (T: Number | Bool | Text | Object): Set(K, V), Get(K) (missing key is a runtime error), GetOr(K, Default), Has(K), Remove(K)->Bool, Count(), IsEmpty(), Clear(). Keys compare exactly (no fractional truncation) — use integer keys. Replaces hand-rolled bucket/chain index lists for id -> row lookups (entity id -> row, structure id -> data).
555
+ - 'const Ids = [10, 20, 30]' (class scope or inside a func; Number/Bool/Text/Vector elements, type inferred) bakes a read-only table into the program: Get/Count/Contains/for-each work, writes are runtime errors. Const tables have zero per-frame snapshot/hash cost — item/monster/config databases belong here, not in runtime lists filled row-by-row at startup.
556
+ - Map<Number, T> — keyed store (T: Number | Bool | Text | Object | Func): Set(K, V), Get(K) (missing key is a runtime error), GetOr(K, Default), Has(K), Remove(K)->Bool, Count(), IsEmpty(), Clear(). Keys compare exactly (no fractional truncation) — use integer keys. Replaces hand-rolled bucket/chain index lists for id -> row lookups (entity id -> row, structure id -> data).
557
+ - Func — reference to one of this class's own funcs: 'var F: Func = Func(OnBuy)' (target must return nothing). F.Invoke(Receiver, args...) runs it — Receiver's class must own the func and args must match its params (runtime errors otherwise); Self is the usual receiver. Defaults to None; not usable in List<T> or @persist, and refs reset to None across save/load — re-seed in OnSpawned. Map<Number, Func> is the dispatch table (kind -> behavior func on one manager class) — replaces one-class-per-behavior. A func that itself Invokes refs cannot be referenced with Func(...) (recursion is a compile error).
516
558
  - Contains/IndexOf: List<Number|Bool|Vector|Position> and object lists only (not List<Text>/List<Timer>).
517
559
  - Sort()/SortDescending(): in-place numeric sort, List<Number> only.
518
560
  - List<struct>.SortByField("FieldName")/SortByFieldDescending: in-place sort of a record list by one of its Number fields (field name is a compile-checked string literal). Ties keep original order.
@@ -551,6 +593,7 @@ Rules:
551
593
  - SavedObject.X .. X is the saved object name
552
594
  - saved clause must have exactly one root group or object (only one entry without parent)
553
595
  - for SavedObject.projectile to work, you need to include 'saved projectile { .. }'
596
+ - shape: saved X { object Y : Class { .. } } - the class goes on the inner object/crowd clause; 'saved X : Class {' is a parse error
554
597
  </saved_objects>
555
598
 
556
599
  <variable_overrides>
@@ -572,10 +615,10 @@ object test : MeleeEnemy { size = (70,70,100), override MaxHP = 100 }
572
615
  - cone/wedge collide as boxes. Walkable ramp = rotated thin cube, e.g. size = (630, 300, 20) rotation = (0, -18, 0) rises toward +X
573
616
  - Marketplace/#mesh models also collide as their AABB box: a boat deck, bridge, or house interior is NOT walkable/enterable by default, and interior furniture's box juts far past its real shape, blocking walkways. Declare `collision = mesh` on the object clause (object Boat { model = "..." collision = mesh }) to collide against the model's actual geometry — the right choice for enterable structures AND for furniture players move around. Skinned/animated models keep the box.
574
617
  - `collision = off` spawns the object with no collider: nothing blocks on it and Touched/Click never fire (detect pickups by distance) — for movers/markers that must not block. Purely decorative geometry is cheaper as a `display` clause.
618
+ - `mirror = true` renders the model as its left/right mirrored twin (same asset, no second model needed) — e.g. a gun or asymmetric prop for the opposite side. Static models only (rigged models ignore it); textures mirror too, so readable text flips.
575
619
  - Colliders are also the hit/UI anchor: Touched/Click/raycasts test the collider box, and hover bars/labels draw on the object they were set on. Size colliders to the visible model and set bars on the visible object, not a hidden collider proxy (symptoms: shots that visually touch but miss; bars floating beside the model).
576
620
  - Player is 40 cm diameter and 180 cm height, to fit inside a 100x100 cm sized tile with gap
577
621
  - Default player spawn: Origin() (0,0,0).
578
- - Calling an Object function without a caller implies 'Self.'
579
622
  - 'for y in 0..8 {', range 0..8 excludes 8 (includes 0 to 7)
580
623
  - '//' for both scene and class code comments (not ##)
581
624
  - List defaults to empty and lazily allocates on first write (Add/Resize/Set).
@@ -702,10 +745,12 @@ Applies to all physics types: Dynamic, Static, and Kinematic.
702
745
  <attachments>
703
746
  - AttachTo(Parent) rigidly parents this object to another: it rides Parent's transform (position + rotation) every tick. The 1-arg form keeps the current relative pose; the overload AttachTo(Parent, LocalPosition, LocalRotationEulerDegrees) mounts explicitly; AttachToAnchor(Parent, "anchor") mounts at a model anchor (e.g. "muzzle", "grip", or a `socket("Name", ...)` declared in the parent's #mesh body — a `bone:`-bound socket makes the child visually ride that bone's animation). Detach() releases (keeps world pose); GetAttachParent() -> Object.
704
747
  - Use attachments for multi-part assemblies (turret on tank, gun in hand, lamp on wall, effect following a part) instead of per-frame SetPosition/LookAt follow loops in forever.
748
+ - Vehicle/cockpit rigs: ONE mover object drives the assembly and every rigid part (cockpit shell, stick, dash, held prop) attaches to it — hand-computing part positions per frame duplicates the seat math across writers and the parts drift apart. The camera is NOT an object and cannot attach: compute eye/look from the mover's own state variables in the same update that moves it (reading the mover's GetPosition() back returns the pre-physics value, so a camera fed from read-back trails one tick).
705
749
  - Children must be collision-free: display/effect/light/marker objects, or call SetCanCollide(false) first. Enforced both ways: AttachTo errors on a collidable child, and SetCanCollide(true) errors while attached (Detach() first). Chains are fine (torso -> arm -> gun); cycles and depth > 16 error.
750
+ - Seats (players riding vehicles/mounts): Car.SeatPlayer(p, "driver") parks a player (user or bot) on that model anchor; Car.SeatPlayerAt(p, "driver", Vector(0,0,60)) is the same for anchor-less primitives. The engine suspends the capsule and native controls, the avatar rides the seat, and the default chase camera follows - no per-frame camera or position writes. Drive per frame from Car.SeatOccupant("driver").GetMoveVectorCamera() (bots: SetAIMoveVector); the car's motion stays your kinematic velocity integration. Exit: Car.UnseatPlayer(p) restores physics in place - TeleportTo an exit spot right after for enclosed cabins. Carjacking = UnseatPlayer(victim) then SeatPlayer(thief); locks, seat roles, and reactions are your script vars. Never fake sitting by hiding+teleporting the player; p.SeatedIn() -> Object tells whether p is seated.
706
751
  - In OnSpawned prefer the explicit overload or AttachToAnchor: cross-object OnSpawned order is unspecified, so 1-arg AttachTo may capture the parent's pre-placement pose. Use 1-arg AttachTo for runtime pickup of already-placed objects.
707
752
  - Local offsets are absolute cm in the parent's local frame, CENTER-to-CENTER: the origin is the parent's center and the offset lands the CHILD's center (NOT the bottom-center pivot GetPosition/#mesh use) - seat a hat by adding half the child's height. Resizing the parent neither rescales nor re-seats mounted children (SetSize also moves the parent's center): re-attach after growth, and prefer ScaleToHeight so the base stays planted.
708
- - While attached, the child's own move/teleport calls are overridden each tick by the attachment. Attachment state is deterministic sim state (rollback/late-join safe).
753
+ - While attached, the child's own move/teleport calls are overridden each tick by the attachment. Attachment state is deterministic sim state (rollback/late-join safe). Destroying the parent detaches children in place (frozen at their last transform, silently): after respawning a parent, re-attach its children.
709
754
  - SetVisible(false) hides only this object; attached children keep their own visibility.
710
755
  - AttachToPositionOnly(Parent) follows Parent's position only (the captured offset still swings with Parent's rotation) while the child's own SetRotation/LookAt keep working — for children that aim independently: beam FX tracking live aim, turret heads, indicators.
711
756
  - Example (turret assembly):
@@ -716,7 +761,7 @@ Applies to all physics types: Dynamic, Static, and Kinematic.
716
761
  </attachments>
717
762
 
718
763
  <easing>
719
- - EaseIn/EaseOut/EaseInOut/EaseOutBack/EaseOutBounce(T) map linear progress T (0..1, clamped) to eased progress. Pair with Lerp/LerpVector to tween anything: positions, rotations, UI opacity, colors, numbers.
764
+ - EaseIn/EaseOut/EaseInOut/EaseOutBack/EaseOutBounce(T) map linear progress T (0..1, clamped) to eased progress. Pair with Lerp/LerpVector to tween positions, rotations, numbers. UI element offset/size/opacity/color: use the built-in glide overloads (SetUiOffset(X, Y, Secs[, Ease]) etc., see <ui>) instead of a loop.
720
765
  - The tween loop (works for any value; write the exact target at the end):
721
766
  var T = 0
722
767
  var Start = O.GetPosition()
@@ -756,10 +801,11 @@ Movement Laws (single-frame vs over-time)
756
801
  <inputs>
757
802
  - Use shared actions for normal gameplay so keyboard, mouse, and mobile touch buttons can drive the same code.
758
803
  - Builtin action constants: Action.Primary, Action.Secondary, Action.Interact, Action.Jump.
759
- - Custom actions (Weave2 worlds): declare a top-level scene clause `action dash { label="Dash" key=Key.LeftShift icon="image.<owner>.<name>" }` (all properties optional; icon is a QUOTED image url). The name then works everywhere builtins do: Action.dash, IsActionPressedByThisPlayer(Action.dash), event ActionDownByThisPlayer[dash]. Prefer custom actions over raw IsKeyPressed for gameplay verbs: they get a mobile touch button (label text) automatically when the world handles their events, and `key=` gives the desktop binding. Up to 27 per world; a key already bound (or used by another action) is a load error. Undeclared action names are a compile error.
804
+ - Custom actions (Weave2 worlds): declare a top-level scene clause `action dash { label="Dash" key=Key.LeftShift icon="image.<owner>.<name>" }` (all properties optional; icon is a QUOTED image url). The name then works everywhere builtins do: Action.dash, IsActionPressedByThisPlayer(Action.dash), event ActionDownByThisPlayer[dash]. Prefer custom actions over raw IsKeyPressed for gameplay verbs: they get a mobile touch button (label text) automatically when the world handles their events, and `key=` gives the desktop binding. Up to 27 per world; a key already bound (or used by another action) is a load error. Undeclared action names are a compile error. Add `params = (name: Number|Text|Position, ...)` (max 4; at most 1 Text, 2 Number, 1 Position) for invoke-only verbs - no key needed, agents and .order scripts call them directly with typed args.
760
805
  - PlayerObject.IsActionPressedByThisPlayer(Action.Primary) .. must be in player class; checks this player's held action.
761
806
  - PlayerObject.GetMoveVectorWorld() / GetMoveVectorCamera() .. this player's normalized move vector; diagonals clamp to length 1, thumbstick magnitudes below 1 are preserved. World = raw input on fixed world axes (W = +Y), camera-independent; Camera = rotated by the player's orbit yaw, which per-frame camera overrides FREEZE - under SetCamera*ThisFrame, derive steering from your authored camera yaw instead.
762
807
  - event ActionDownByThisPlayer[Primary] / ActionUpByThisPlayer[Primary] .. must be in player class; options are the builtins plus declared action names.
808
+ - event ActionInvokedByThisPlayer[dash] .. fires when a parameterized action is invoked (not for key edges); read args via Event.ActionText / Event.ActionNumber / Event.ActionNumber2 / Event.ActionPosition (filled from the def's params, matched by type).
763
809
  - Current builtin bindings: Left mouse -> Primary, Right mouse -> Secondary, E -> Interact, Space -> Jump.
764
810
  - Current movement bindings: WASD and mobile thumbstick feed the shared move vector.
765
811
  - Mobile world taps still drive pointer/mouse click APIs. Visible mobile action buttons feed held/shared Action.* state/events when the compiled Weave uses matching action events (builtin and custom alike).
@@ -820,8 +866,10 @@ Movement Laws (single-frame vs over-time)
820
866
  <camera>
821
867
  - default camera is third person with mouse-look (like fortnite).. mouse target will just be crosshair target
822
868
  - SetIsMouseLookOn(true) is the default mouse look like counter-strike and fortnite (SetCameraToFirstPerson also turns it on): the cursor is hidden and pinned to screen center, so players CANNOT click UI buttons — call SetCursorUiMode(true) while a menu/shop/end screen is open (releases look AND shows the cursor), SetCursorUiMode(false) on close (touch players can still tap). Spectator + per-frame camera overrides (SetCameraLookAtThisFrame) keep the cursor pinned+hidden while the script owns the camera angles: commander-camera/RTS worlds call SetCursorUiMode(true) once at player spawn
823
- - Camera always follows PlayerObject every frame unless overridden by SetCameraThisFrame()/SetCameraLookAtThisFrame()/SetChaseCameraThisFrame() (each is a one-frame override; call every frame to keep it) — or parked with SetCameraFixed(Position, LookAt), the persistent one-call variant. While a per-frame override or fixed camera is active the mouse no longer steers the camera. The camera always views the caller's own PlayerObject; no simcall views through another player's object.
824
- - SetCameraFixed(Position, LookAt) parks the camera (security cam / lobby / cinematic shot): persists across frames AND save/reload, camera collision is off while fixed, mouse-look is ignored; any camera-mode switch (SetCameraToThirdPerson(...) etc.) releases it SetIsMouseLookOn/SetCursorUiMode do NOT. Prefer it over a per-frame SetCameraLookAtThisFrame loop for static shots.
869
+ - Camera always follows PlayerObject every frame unless overridden by SetCameraThisFrame()/SetCameraLookAtThisFrame()/SetChaseCameraThisFrame() (each is a one-frame override; call every frame to keep it) — or parked with SetCameraFixed(Position, LookAt), the persistent one-call variant. While a per-frame override or fixed camera is active the mouse no longer steers the camera. The camera always views the caller's own PlayerObject; no simcall views through another player's object. While the sim is paused scripts don't run, so per-frame overrides lapse and the view falls back to the default rig until unpause (SetCameraFixed persists through pause).
870
+ - Camera follows your own gameplay actor when it is a separate object from PlayerObject: prefer making PlayerObject the actual mover; otherwise call SetCameraPivotTargetThisFrame(Target.GetPosition()) every frame. Never chase the actor by per-frame PlayerObject SetPosition each write re-anchors prediction and the whole view back-snaps under real latency (GetPosition is also pre-physics: one frame stale). Following ANOTHER player's object this way snaps with their prediction corrections; there is no smoothed remote-follow primitive yet.
871
+ - SetCameraFixed(Position, LookAt) parks the camera (security cam / lobby / cinematic shot): persists across frames AND save/reload, camera collision is off while fixed, mouse-look is ignored; any camera-mode switch (SetCameraToThirdPerson(...) etc.) releases it — SetIsMouseLookOn/SetCursorUiMode do NOT. Prefer it over a per-frame SetCameraLookAtThisFrame loop for static shots — and never re-issue it per frame on a moving position: it fights camera smoothing and the view bounces (moving cameras belong to the ThisFrame overrides).
872
+ - SetCameraRollThisFrame(Degrees) tilts the view about the look axis (one-frame override; call every frame to hold) — works in every camera mode; cursor picking/world-anchored UI stay level, so keep it to tilt/shake/lean.
825
873
  - Default camera yaw/rotation is 0 degrees.
826
874
  - At camera yaw 0: camera forward is +X, camera up is +Z, and camera left is +Y.
827
875
  - Camera yaw is right-hand (same as object yaw): `SetCameraRotation(0)` faces +X, `SetCameraRotation(90)` faces +Y, `SetCameraRotation(-90)` faces -Y.
@@ -829,7 +877,7 @@ Movement Laws (single-frame vs over-time)
829
877
  - Angles in this API are degrees. Trig is Number methods on the angle: `Angle.SinDegree()`/`CosDegree()`/`TanDegree()`, `Y.Atan2Degree(X)` (degrees); `.Sin()`/`.Cos()`/`.Tan()`/`.Atan2(X)` are radians. There are no global trig functions.
830
878
  - SetCameraToTopDown() defaults to 70-degree tilt down, 900 units zoom, facing +Y.
831
879
  - Camera-mode switch signatures: `SetCameraToTopDown()`, `SetCameraToFirstPerson()`, `SetCameraToSideView()` take no args; only `SetCameraToThirdPerson(IsMouseLookOn)` takes one Bool. Switches restore collision+gravity, except an active SetPlayerModeHover/Spectator survives (SetPlayerModeGrounded exits it).
832
- - 2D-style games (top-down or side-view camera) are still 3D worlds: keep a ground/background object so the camera never shows bare skybox, and for side-view keep gameplay on a constant Y plane. A compact ~500x500 cm play area suits board/arcade layouts (chess, brick-breaker).
880
+ - 2D-style games (top-down or side-view camera) are still 3D worlds: keep a ground/background object so the camera never shows bare skybox, and for side-view keep gameplay on a constant Y plane. A compact ~500x500 cm play area suits board/arcade layouts (chess, brick-breaker). If the user explicitly asks for 2D, a full-canvas screenCanvas game is a legitimate shape for hand-scale genres (cards/board/puzzle) — scrolling games stay filmed-3D; say which you chose.
833
881
  - Third-person body faces its MOVEMENT direction, not the camera; GetCrosshairDirection() is camera-only. So a gun/item aimed via crosshair won't match the body when looking around while standing still. Fix: SetOrientingWithCameraYaw(true) makes the body track the camera yaw.
834
882
  - SetCameraZoom .. 0 will make it first person, 900 will make it third-person with camera 900 units away from character (zoom alone never enables mouse steering; use SetCameraToFirstPerson() for playable first person)
835
883
  - Third-person default framing: zoom 525 with pivot offset (0, -39, 87) = over-the-shoulder, body slightly left of screen center; override via SetCameraZoom()/SetCameraPivotOffset()
@@ -857,7 +905,7 @@ event OnSpawned {
857
905
  forever { MoveInDirectionContinuously(GetMoveVectorCamera(), 620) }
858
906
  }
859
907
  #end
860
- - lean first-person player. SetCameraToFirstPerson() puts the camera at the eye and hides this client's own body (remote players still see it); EnableLocalWeaponView draws the equipped weapon as a camera-attached viewmodel:
908
+ - lean first-person player. SetCameraToFirstPerson() puts the camera at the eye and hides this client's own body and held weapon (remote players still see them); EnableLocalWeaponView draws the equipped weapon plus auto-arms as one camera-attached viewmodel - Offset/RotationDegrees move them together (camera axes, cm: +X right, +Y up, +Z forward; Rotation (0,0,0) = barrel forward), the arms replay the player's upper-body clip (PlayUpperBodyAnimation) and ignore bone overrides:
861
909
  #class player
862
910
  event OnSpawned {
863
911
  SetCameraToFirstPerson()
@@ -915,7 +963,7 @@ SetShadowDepthBiasConstant(Units: Number) // Default: 1
915
963
  SetShadowDepthBiasSlopeScale(Scale: Number) // Default: 2.5
916
964
  SetShadowOpacity(Opacity01: Number) // Default: 1 [global: all clients]
917
965
  SetStaticLodTransitions(Lod1Size: Number, Lod2Size: Number, Lod3Size: Number) // Default: 0.18, 0.07, 0.025
918
- SetStaticLodShadowTransitions(Lod1Size: Number, Lod2Size: Number, Lod3Size: Number) // Same screen-size units as SetStaticLodTransitions, but for meshes drawn into the sun-shadow maps (defaults higher: shadows tolerate earlier LOD). Lower toward the view values if a large caster's shadow silhouette looks low-poly or pops. Default: 0.35, 0.18, 0.08
966
+ SetStaticLodShadowTransitions(Lod1Size: Number, Lod2Size: Number, Lod3Size: Number) // LOD thresholds for meshes drawn into sun-shadow maps, as the caster's fraction of the active cascade's coverage box (NOT camera screen units - far cascades cover more ground, so casters coarsen there naturally). Lower if a shadow silhouette looks low-poly or pops; raise to cheapen shadows. Default: 0.35, 0.18, 0.08
919
967
  SetContrast(Contrast: Number) // Power curve around mid-gray, pre-tonemap: shadows compress, never crush to black. Default: 1 = neutral; 0 = flat gray, 2 = strong [global: all clients]
920
968
  SetSaturation(Saturation: Number) // Saturation multiplier. Default: 1 = neutral; 0 = grayscale, 2 = vivid [global: all clients]
921
969
  SetExposure(Exposure: Number) // EV stops, pre-tonemap: +1 = twice as bright, -1 = half. Default: 0 [global: all clients]
@@ -972,6 +1020,8 @@ SetAssetSubmeshReceiveShadow(ModelAsset: Object, SubmeshIndex: Number, IsOn: Boo
972
1020
  SetAssetSubmeshReceiveDecal(ModelAsset: Object, SubmeshIndex: Number, IsOn: Bool)
973
1021
  SetAssetSubmeshMaterial(ModelAsset: Object, SubmeshIndex: Number, MaterialAsset: Object)
974
1022
  ClearAssetSubmeshMaterial(ModelAsset: Object, SubmeshIndex: Number) // Clears explicit submesh material override; reverts to authored/default material
1023
+ SetAssetSubmeshFur(ModelAsset: Object, SubmeshIndex: Number, LengthCm: Number, Density: Number) // Soft shell fur on a rigged model's submesh, keeping its textures: LengthCm 0..20 cm (0 = off), Density = relative strand fineness 0.1..8 (0 = default 1). Asset-scoped (all instances share it); skinned models only; real overdraw cost - hero creatures
1024
+ SetAssetSubmeshFurMask(ModelAsset: Object, SubmeshIndex: Number, Mask: Object) // Per-texel fur length over a SetAssetSubmeshFur submesh: Mask = Image.X in the model's UV space (fur_mask_gen bakes one as Ori.projimg.<handle>; or any Ori.image.<user>.<name>), R x LengthCm: 0 = bare, 1 = full; None clears
975
1025
  SetMaterialParameterNumber(MaterialAsset: Object, Slot: Number, Value: Number)
976
1026
  SetMaterialParameterTexture(MaterialAsset: Object, Slot: Number, ImageAsset: Object)
977
1027
  GetMaterialParameterNumber(MaterialAsset: Object, Slot: Number) -> Number
@@ -982,7 +1032,7 @@ Number.ToText() -> Text
982
1032
  Bool.ToText() -> Text
983
1033
  Position.ToText() -> Text
984
1034
  ColorHex(Hex: Text) -> Position
985
- RandomNumber(From: Number, To: Number) -> Number // To exclusive; From == To returns From.
1035
+ RandomNumber(From: Number, To: Number) -> Number // To exclusive; From == To returns From. Returns a fractional Number - .RoundDown() it for integer draws.
986
1036
  RandomChancePercent(Percent: Number) -> Bool // Inside player input events: fully predictable (predicted result = final result). Elsewhere it returns false during client prediction (corrected on confirm), so use it only for delay-tolerant side effects like item drops.
987
1037
  RandomPositionWithinRegion(Region: Object) -> Vector
988
1038
  RandomPositionInDisc(Center: Position, Radius: Number) -> Vector
@@ -1047,7 +1097,7 @@ LaunchObject(SavedObject: Object, Position: Position, Speed: Number, Direction:
1047
1097
  SpawnCanvas(SavedObject: Object, Position: Position) -> Object
1048
1098
  SpawnChildUI(SavedObject: Object, ParentUI: Object) -> Object
1049
1099
  GetAnimationDuration(AnimationAsset: Object) -> Number
1050
- DrawPreview(SavedObject: Object, Position: Position, Rotation: Vector) // DrawPreview input can be SavedObject.X
1100
+ DrawPreview(SavedObject: Object, Position: Position, Rotation: Vector) // DrawPreview input can be SavedObject.X; draws on every client - DrawPreviewForPlayer for one player's screen
1051
1101
  DrawPreviewWithSizeAndColor(SavedObject: Object, Position: Position, Rotation: Vector, Size: Vector, Color: Vector)
1052
1102
  Position.NearestObjectWithSavedType(SavedObject: Object) -> Object
1053
1103
  Position.NearestObjectWithTag(Tag: Number) -> Object
@@ -1199,7 +1249,7 @@ Object.SetButtonBackgroundOpacity(Opacity01: Number)
1199
1249
  Object.SetUiOffset(X: Number, Y: Number)
1200
1250
  Object.SetUiSize(Width: Number, Height: Number)
1201
1251
  Object.SetUiColor(Color: Vector)
1202
- Object.SetUiOpacity(Opacity01: Number)
1252
+ Object.SetUiOpacity(Opacity01: Number) // PER-ELEMENT fade, multiplied over the authored alpha (colors untouched; 1 = the authored look, 0 = invisible); children keep their own opacity - hiding a subtree is SetVisible's job (that DOES cascade). Hit-testing unaffected. Also authorable: `opacity = 0.5`.
1203
1253
  Object.SetUiImage(ImageAsset: Object)
1204
1254
  Object.SetUiAnchor(AnchorX: Number, AnchorY: Number) // Place inside a plain-frame parent. X/Y: 0=left/top, 1=center, 2=right/bottom. Sets anchor mode.
1205
1255
  Object.SetUiPivot(PivotX: Number, PivotY: Number) // Pivot of this element for anchor placement. X/Y: 0=left/top, 1=center, 2=right/bottom. Sets anchor mode.
@@ -1212,8 +1262,8 @@ Object.SetUiColumns(Columns: Number) // Grid column count. Grid container only.
1212
1262
  Object.SetUiRows(Rows: Number) // Grid row count (0 = derive from children). Grid container only.
1213
1263
  Object.SetUiChildAlign(ChildAlignX: Number, ChildAlignY: Number) // Default child alignment. X/Y: 0=start, 1=center, 2=end. Container only.
1214
1264
  Object.Destroy()
1215
- Object.Die()
1216
- Object.DieWithSpeed(Speed: Number, DissolveSpeed: Number)
1265
+ Object.Die() // Plays the death anim + corpse; a NON-PLAYER object then dissolves and DESPAWNS. Players: warp-only reset, never despawned.
1266
+ Object.DieWithSpeed(Speed: Number, DissolveSpeed: Number) // Die() with anim Speed + corpse DissolveSpeed; same despawn rule.
1217
1267
  Object.Attack(Defender: Object, Damage: Number)
1218
1268
  Object.AddPhysicsVelocity(Velocity: Vector)
1219
1269
  Object.SetPhysicsVelocity(Velocity: Vector)
@@ -1237,7 +1287,7 @@ Object.IsMouseLookOn() -> Bool
1237
1287
  Object.IsMouseVisible() -> Bool
1238
1288
  Object.IsCrosshairVisible() -> Bool
1239
1289
  Object.IsOrientingWithCameraYaw() -> Bool
1240
- Object.SetIsMouseLookOn(IsMouseLookOn: Bool)
1290
+ Object.SetIsMouseLookOn(IsMouseLookOn: Bool) // true engages pointer lock on web (browser may require a click in the canvas first; until locked GetMousePositionDelayed keeps returning the last cursor position) and hides the cursor; false frees the cursor - it can then leave the game view, where GetMousePositionDelayed FREEZES at the last in-view position. Mouse-flown vehicles: keep mouse-look OFF and steer from cursor position vs screen center
1241
1291
  Object.SetMouseVisible(IsMouseVisible: Bool)
1242
1292
  Object.SetCrosshairVisible(IsCrosshairVisible: Bool)
1243
1293
  Object.SetOrientingWithCameraYaw(IsOrientingWithCameraYaw: Bool) // true: body yaw tracks camera every frame (aim/strafe mode), so a held/aimed weapon stays aligned with the body; takes precedence over Move*Facing calls' facing. Off by default: body only turns when moving.
@@ -1246,7 +1296,7 @@ Object.SetCameraZoom(Distance: Number)
1246
1296
  Object.SetCameraFieldOfView(FieldOfView: Number)
1247
1297
  Object.SetCameraTiltDown(PitchDown: Number)
1248
1298
  Object.SetCameraRotation(YawDegrees: Number)
1249
- Object.SetCameraPivotTargetThisFrame(Target: Vector)
1299
+ Object.SetCameraPivotTargetThisFrame(Target: Vector) // One-frame pivot override: the camera follows Target instead of the player (call every frame to hold; display-only, no sim state). THE lane for camera-tracks-another-object (units, vehicles, spectate) - never per-frame SetPosition on a hidden player as a camera proxy: players are prediction-owned, rollback snaps the whole view.
1250
1300
  Object.SetCameraToFirstPerson()
1251
1301
  Object.SetCameraToThirdPerson(IsMouseLookOn: Bool)
1252
1302
  Object.SetCameraToTopDown()
@@ -1258,7 +1308,7 @@ Object.SetCameraUserZoomLimits(MinDistance: Number, MaxDistance: Number)
1258
1308
  Object.SetCameraCollisionEnabled(IsEnabled: Bool)
1259
1309
  Object.SetPlayerModeGrounded() // Kinematic character: gravity ON, PreventOverlap ON, collidable
1260
1310
  Object.SetPlayerModeHover() // Kinematic hover: gravity OFF, PreventOverlap ON, collidable
1261
- Object.SetPlayerModeSpectator() // Non-physical noclip spectator. Does NOT hide the player automatically.
1311
+ Object.SetPlayerModeSpectator() // Non-physical noclip spectator; hides the avatar (call SetVisible(true) after to keep it shown). Leaving spectator restores it.
1262
1312
  List.Add(Element: T)
1263
1313
  List.Get(Index: Number) -> T
1264
1314
  List.Set(Index: Number, Element: T)
@@ -1312,23 +1362,23 @@ WorldToTile(Position: Position) -> Position
1312
1362
  TileToWorld(TileX: Number, TileY: Number) -> Position
1313
1363
  TileToWorld(TileXYZ: Vector) -> Position
1314
1364
  TileToWorld(TileX: Number, TileY: Number, TileZ: Number) -> Position
1315
- TileGridCreate(TilesX: Number, TilesY: Number, DefaultWalkable: Bool) -> List
1365
+ TileGridCreate(TilesX: Number, TilesY: Number, OriginTile: Vector, DefaultWalkable: Bool) -> List
1316
1366
  TileGridSetWalkable(Grid: List, X: Number, Y: Number, Walkable: Bool)
1317
1367
  TileGridIsWalkable(Grid: List, X: Number, Y: Number) -> Bool
1318
1368
  BuildTileBlockedGridFromTag(OriginTile: Vector, TilesX: Number, TilesY: Number, BlockerTag: Number) -> List
1319
1369
  TileGridFindPath4(Grid: List, StartX: Number, StartY: Number, GoalX: Number, GoalY: Number) -> List
1320
1370
  TileGridFindPath8(Grid: List, StartX: Number, StartY: Number, GoalX: Number, GoalY: Number) -> List
1321
- TileGridPathTile(Grid: List, TileId: Number, OriginTile: Vector) -> Vector
1322
- TileGridPathWorld(Grid: List, TileId: Number, OriginTile: Vector) -> Position
1371
+ TileGridPathTile(Grid: List, TileId: Number) -> Vector
1372
+ TileGridPathWorld(Grid: List, TileId: Number) -> Position
1323
1373
  Object.GetTile() -> Position
1324
1374
  Object.GetMouseTargetTileOnZeroZ() -> Position // Player only. Tile index (hit/TileSizeXY floored) of the cursor ray on z=0; Invalid on miss - gate with .IsValid()
1325
1375
  Object.WorldToScreenPosition(Position: Position) -> Vector // Base UI viewport coords (1600x900). Returns Vector(-1,-1,0) if behind/unprojectable.
1326
1376
  Object.LocalOffsetToWorld(OffsetLocal: Position) -> Position // Origin: GetPosition()
1327
1377
  Object.LocalDirectionToWorld(DirLocal: Vector) -> Vector
1328
- Object.EquipWeapon(WeaponModel: Object) // attach a weapon model to this object's hand (render) + enable weapon anchors (sim). Use for ALL player-held items - never a per-frame position-follow loop: scripts read sim aim, which trails the instantly-rendered camera, so the item swings ~100ms late on every turn. Aim/strafe games: add SetOrientingWithCameraYaw(true). Models with no grip data are held at a bbox guess (mid-body grips): give #mesh weapons socket("grip"); fix library/store models with SetModelAnchor
1378
+ Object.EquipWeapon(WeaponModel: Object) // attach a weapon model to this object's hand (render) + enable weapon anchors (sim). Use for ALL player-held items - never a per-frame position-follow loop: scripts read sim aim, which trails the instantly-rendered camera, so the item swings ~100ms late on every turn. Aim/strafe games: add SetOrientingWithCameraYaw(true). Models with no grip data are held at a bbox guess (mid-body grips): give #mesh weapons socket("grip"); fix library/store models with SetModelAnchor. While a Play*/SetAnimation clip runs it owns the whole skeleton: the auto aim-lean and the weapon's hold-idle pause until locomotion retakes (PlayUpperBodyAnimation layers persist); during auto locomotion the hold-idle owns the upper body - if the model's own clips already pose the weapon, SetUpperBodyAnimationWeight(0, 0) releases it (weapon stays in hand)
1329
1379
  Object.UnequipWeapon() // remove the held weapon
1330
1380
  Object.GetHeldWeapon() -> Object // held weapon model, or InvalidId when unarmed
1331
- Object.GetWeaponAnchorWorldPos(AnchorName: Text) -> Position // AnchorName can be any model anchor; returns held-weapon anchor position in world space. Market-model anchors register only once the model has downloaded (OnSpawned can be too early); SetModelAnchor anchors are available immediately
1381
+ Object.GetWeaponAnchorWorldPos(AnchorName: Text) -> Position // AnchorName can be any model anchor; returns held-weapon anchor position in world space. Market-model anchors register only once the model has downloaded (OnSpawned can be too early); SetModelAnchor anchors are available immediately. First person: anchors live on the HIDDEN third-person weapon near the body, not the viewmodel - spawn muzzle VFX on the aim ray (origin + dir * ~80) instead
1332
1382
  Object.LookAtTileHorizontal(TileX: Number, TileY: Number)
1333
1383
  Object.LookAtTileHorizontal(TileX: Number, TileY: Number, TileZ: Number)
1334
1384
  Object.LookAtTileHorizontal(TileXYZ: Vector)
@@ -1350,7 +1400,7 @@ Object.SetRotation(Rotation: Vector) // Rotates about GetPosition() (bottom-cent
1350
1400
  Object.SetRotation(X: Number, Y: Number, Z: Number) // Rotates about GetPosition() (bottom-center pivot for mesh objects). To spin about the middle: SetRotation(R) then SetCenterPosition(C) with C the fixed world center; any other hinge point: SetRotationWithPivot
1351
1401
  Object.SetGroupRotationWithPivot(Pivot: AnyBasicType, Rotation: Vector)
1352
1402
  Object.LookAtGroupWithPivot(Pivot: AnyBasicType, Target: AnyBasicType)
1353
- Object.SetRotationWithPivot(Pivot: Position, Rotation: Vector) // Part swings around its middle instead of its mount (wing root, tail base, door edge) -> rigid-rotate about world-space Pivot: sets Rotation and moves GetPosition accordingly. Rejects gravity/force receivers. Group receivers: SetGroupRotationWithPivot
1403
+ Object.SetRotationWithPivot(Pivot: Position, Rotation: Vector) // Part swings around its middle instead of its mount (wing root, tail base, door edge) -> rigid-rotate about world-space Pivot: sets Rotation and moves GetPosition accordingly. The position orbit composes from the CURRENT pose each call, so keep this the part's SOLE mover - interleaving another per-frame position writer compounds drift (for per-frame driving prefer SetRotation + SetCenterPosition). Rejects gravity/force receivers. Group receivers: SetGroupRotationWithPivot
1354
1404
  Object.SetScale(Scale: Vector) // raw multiplier of the model's authored size; prefer SetSize (world-unit footprint) for world objects AND effects (effects: size scales vs their boundingBoxSize)
1355
1405
  Object.SetScale(X: Number, Y: Number, Z: Number) // raw multiplier of the model's authored size; prefer SetSize (world-unit footprint) for world objects AND effects (effects: size scales vs their boundingBoxSize)
1356
1406
  Object.SetSize(Size: Vector) // Resizes around the object's CENTER: a grounded object sinks by half the growth (re-ground after, or use ScaleToHeight which stays base-anchored). Per-axis values stretch imported models - prefer uniform scaling for marketplace meshes
@@ -1365,7 +1415,7 @@ Object.ScaleToHeight(Height: Number) // Uniform scale anchored at the base/botto
1365
1415
  Object.SnapToGround() // Snap bottom-center downward to first collider; ignores prior Z offsets
1366
1416
  Object.SnapToGroundWithTag(Tag: Number)
1367
1417
  Object.SnapToGroundIgnoreTag(Tag: Number)
1368
- Object.SetColor(RGB: Vector) // Tint that MULTIPLIES the material's existing colors (white = unchanged, black = black); it does not replace the albedo
1418
+ Object.SetColor(RGB: Vector) // Tint that MULTIPLIES the material's existing colors (white = unchanged, black = black); it does not replace the albedo. On light objects this IS the emitted light color - runtime light retint needs no other call.
1369
1419
  Object.SetColor(R: Number, G: Number, B: Number)
1370
1420
  Object.SetOpacity(Opacity01: Number)
1371
1421
  Object.SetDebugMetallic(Metallic01: Number)
@@ -1415,16 +1465,35 @@ Object.SetHoverHPBar(HP: Number, MaxHP: Number)
1415
1465
  Object.SetHoverSPBar(SP: Number, MaxSP: Number)
1416
1466
  Object.SetHoverBar(BarIndex: Number, Value: Number, MaxValue: Number, Color: Vector, BackgroundColor: Vector, OffsetX: Number, OffsetY: Number, Width: Number, Height: Number) // Floating stat bar above the caller at BarIndex 0..=29 (30/31 are the built-in SP/HP bars — use SetHoverSPBar/SetHoverHPBar). MaxValue <= 0 hides the bar. Overlapping bars on one object auto-separate toward their intended side; offsets need not be pixel-exact
1417
1467
  Object.ClearHoverBars() // Remove all of the caller's hover bars; SetHoverText("") clears the label. Objects reused without Destroy (repositioned indicators, manual pools) keep their previous life's bars and label - clear on repurpose
1418
- Object.SetHoverText(Text: Text) // Persistent label above the object (unit names, vendor tags); empty Text removes it
1468
+ Object.SetHoverText(Text: Text) // Persistent label above the object (unit names, vendor tags); empty Text removes it. Keeps a color set by the 2-arg overload
1469
+ Object.SetHoverText(Text: Text, Color: Vector) // SetHoverText with a label color (e.g. per-player name tags); the 1-arg form keeps it
1419
1470
  SetPlayerNamesVisible(IsVisible: Bool) // Default: visible. Hide the engine's username labels when drawing custom nameplates
1420
1471
  DrawLine(Start: Position, End: Position, Color: Vector, Thickness: Number)
1421
1472
  DrawSphere(Position: Position, Radius: Number, Color: Vector)
1422
1473
  DrawSphere(Position: Position, Radius: Number)
1423
1474
  DrawSkillAreaCircle(Position: Position, Radius: Number, Color: Vector)
1424
1475
  DrawDot(Position: Position)
1425
- SpawnEffect(Effect: Object, Position: Position) -> Object
1476
+ SpawnEffect(Effect: Object, Position: Position) -> Object // Persists until Destroy() - per-frame/loop spawns accumulate and tank fps; one-shots: SpawnEffectWithAutoDestroy, timed: SpawnEffectForDuration.
1426
1477
  SpawnEffectWithAutoDestroy(Effect: Object, Position: Position) -> Object
1427
1478
  SpawnEffectForDuration(Effect: Object, Position: Position, Duration: Number) -> Object
1479
+ SpawnEffectAttached(Effect: Object, Parent: Object, AnchorName: Text, Duration: Number) -> Object // Spawns attached to Parent's anchor and follows it exactly; anchors like "muzzle" resolve through the equipped weapon when Parent's model lacks them. Duration<=0 = effect's own lifetime. Prefer over spawn-then-reposition for muzzle flashes/exhausts.
1480
+ Object.SetCell(X: Number, Y: Number, Palette: Number) // TileMap objects only. Paint one grid cell (0-based, y=0 south) to palette slot Palette (0 = empty; the slot must exist in this tilemap's palette). Shared assets auto-clone for this object. Errors: out-of-range cell/palette, or emptying the whole map
1481
+ Object.SetCellsRect(X1: Number, Y1: Number, X2: Number, Y2: Number, Palette: Number) // TileMap objects only. Paint the inclusive rect to one slot; endpoints in any order; <= 4096 cells per call (chunk bigger fills). Roads/fields/forest layers at scale - cells, not thousands of objects; growth stages = palette ids, harvest = a cell edit
1482
+ Object.CellAt(X: Number, Y: Number) -> Number // TileMap objects only. Palette slot at the cell (0 = empty). Out-of-range errors - bound scans with CellCounts()
1483
+ Object.CellCounts() -> Vector // TileMap objects only. (width, height, 0) in cells
1484
+ Object.EnableLocalPlacementGhost(Model: Object, TileSizeCm: Number, FootprintW: Number, FootprintH: Number, ValidColor: Vector, InvalidColor: Vector, Opacity: Number) // PlayerObject only. Local zero-latency build ghost: Model (SavedObject clone or Model.X) snaps to the live cursor's ground hit on a TileSizeCm grid with a FootprintW x FootprintH tile footprint. Visual only - run CanPlace + place on click/drag events (SetLocalPlacementGhostState feeds the color). Hidden while cursor is hidden/mouse-look
1485
+ Object.SetLocalPlacementGhostState(YawDegrees: Number, IsValid: Bool) // PlayerObject only. Per-frame ghost pose/verdict: YawDegrees rotates the model (quarter turns swap the footprint axes), IsValid picks ValidColor/InvalidColor
1486
+ Object.SetLocalPlacementGhostMode(Mode: Number) // PlayerObject only. 0 = single footprint (default), 1 = axis-locked drag line (roads), 2 = drag rect; 1/2 preview cells from left-drag start (or the SetLocalPlacementGhostAnchor pin) to cursor
1487
+ Object.DisableLocalPlacementGhost() // PlayerObject only. Hides the local build ghost
1488
+ Object.SetLocalPlacementGhostAnchor(GroundPos: Position) // PlayerObject only. Pin the mode-1/2 preview start to a world ground position (replaces the left-drag start while set): click-move-click roads - click 1 sets the anchor, the ghost previews anchor->cursor with no button held; commit + Clear on click 2 stay in script. Rejects invalid positions; Enable/Disable also clear it
1489
+ Object.ClearLocalPlacementGhostAnchor() // PlayerObject only. Back to left-drag anchoring
1490
+ Object.SeatPlayer(Target: Object, AnchorName: Text) -> Bool // Park Target player (user or SpawnAIPlayer bot) on this object's named model anchor: the capsule suspends (collision/gravity/native controls) and the avatar rides the anchor until UnseatPlayer. False if the seat is occupied or Target is already seated/attached or in editor mode. Input reads keep flowing while seated - drive the vehicle from the occupant's GetMoveVectorCamera()/IsActionPressedByThisPlayer
1491
+ Object.SeatPlayerAt(Target: Object, SeatName: Text, Offset: Vector) -> Bool // SeatPlayer for objects without model anchors (primitive-built vehicles). SeatName is the seat key SeatOccupant queries; Offset is from this object's CENTER in its local axes (cm, rotates with it)
1492
+ Object.UnseatPlayer(Target: Object) -> Bool // Release a seated player at the seat's current world position, physics restored per their player mode (TeleportTo an exit spot right after for enclosed vehicles). False if Target is not seated on this object
1493
+ Object.SeatOccupant(SeatName: Text) -> Object // Player seated on this object's named seat, or a None-like Object (check Exists())
1494
+ Object.SeatedIn() -> Object // Object this player is seated on, or a None-like Object (check Exists())
1495
+ TileGridRefreshFromTerrainRect(Grid: List, X1: Number, Y1: Number, X2: Number, Y2: Number)
1496
+ TileGridDebugOverlay(Grid: List, Enabled: Bool)
1428
1497
  SpawnDecal(Material: Object, Position: Position, RotationEuler: Vector, Size: Vector) -> Object // Projects onto geometry inside its oriented box along local -Z (down at rotation 0); Size z = projection depth cm. Bullet holes/stains/scorch marks. No engine pool: keep spawned decals in a List and Destroy the oldest past a few hundred
1429
1498
  SetScreenShader(Material: Object) // Fullscreen screen effect (film grain, color grade, distortion, damage flash): a #shader material applied after tonemap/AA; fragment reads ctx.color/ctx.depth. None clears. Animate via SetMaterialParameterNumber; see shader_dsl skill
1430
1499
  SetVolumetricLightIntensity(Percent: Number) // Sun shafts / god rays (raymarched vs the sun's shadows). Default: 0 (off); ~40 = subtle atmosphere, 100+ = dramatic alley/doorway shafts. Reads best with a low sun + occluding geometry
@@ -1453,9 +1522,9 @@ SetSunColor(R: Number, G: Number, B: Number)
1453
1522
  SetFogColor(Color: Vector) [global: all clients]
1454
1523
  SetFogColor(R: Number, G: Number, B: Number)
1455
1524
  LoadWorldTransferClear()
1456
- LoadWorldTransferPush(Value: Number)
1525
+ LoadWorldTransferPush(Value: Number) // append a Number carried to the next stage/restart of THIS project (survives SwitchStage/RestartGame; loading a different world clears the buffer). Clear + push a version tag first to guard stale reads.
1457
1526
  LoadWorldTransferCount() -> Number
1458
- LoadWorldTransferGet(Index: Number) -> Number
1527
+ LoadWorldTransferGet(Index: Number) -> Number // transfer value at Index (0 if out of range).
1459
1528
  GetVignetteStrength() -> Number
1460
1529
  GetVignetteRadius() -> Number
1461
1530
  GetVignetteSoftness() -> Number
@@ -1464,6 +1533,7 @@ GetGravity() -> Number
1464
1533
  PersistGetNumber(Player: Object, Key: Text) -> Number
1465
1534
  PersistSetNumber(Player: Object, Key: Text, Value: Number)
1466
1535
  PersistAddNumber(Player: Object, Key: Text, Delta: Number)
1536
+ StatsEmit(Metric: Text, Key1: Text, Key2: Number, Value: Number) // run_orders grading probe: emit each graded metric once per second (zeros included); expects read the last sample at or before their frame, optionally key-scoped as <metric>[<Key1>]. Key2 is a free tag, not matched
1467
1537
  RefreshStaticBaseline() // Call once at the end of heavy static-scene init; non-moving objects then skip per-frame display CPU (a later manipulation opts that object out).
1468
1538
  RestartGame() // Reloads the WHOLE project (fresh load flow; discards the user's unsaved manual editor edits). Hard New Game only - pair with PersistClear(). Round/match resets: reset positions/scores in code instead.
1469
1539
  AnyBasicType.NearestPlayerNonSpectator() -> Object
@@ -1479,7 +1549,7 @@ SpawnTerrainFromHeights(Name: Text, Heights: List, SamplesX: Number, SamplesY: N
1479
1549
  Object.SetTerrainMaterial(MaterialAsset: Object) // Terrain objects only; Material.X (#shader or #texture auto-material, triplanar works), None clears
1480
1550
  Object.SetTerrainLayers(Steep: Object, High: Object, Low: Object) // Terrain only; blends extra #texture materials over the base by slope/height: Steep on cliffs, High above HighStartZ, Low below LowStartZ. None skips a layer. Set base via SetTerrainMaterial
1481
1551
  Object.SetTerrainLayerParams(SlopeStartDeg: Number, SlopeEndDeg: Number, HighStartZ: Number, HighEndZ: Number, LowStartZ: Number, LowEndZ: Number) // Terrain only; tunes SetTerrainLayers blending. Slope in degrees (start..end ramp), heights in world Z cm. 0,0 for a pair = auto (slope 32..50, High top ~25%, Low bottom ~15% of height range)
1482
- Object.AddInstance(Offset: Vector, RotationEuler: Vector, Size: Vector, ColorRGB: Vector) // Batch only. Offset cm from batch origin (within +-30000), RotationEuler deg, Size cm, ColorRGB 0..1
1552
+ Object.AddInstance(Offset: Vector, RotationEuler: Vector, Size: Vector, ColorRGB: Vector) // Batch only. Offset cm from batch origin (within +-30000), RotationEuler deg, Size cm, ColorRGB 0..1. Offset is the instance CENTER, not the bottom pivot object positions use - half-sunk scatter means add Size.z/2 to z
1483
1553
  Object.ClearInstances() // Batch only; removes all instances
1484
1554
  Object.GetInstanceCount() -> Number // Batch only
1485
1555
  Object.SetBatchOrigin(Origin: Position) // Batch only; moves whole batch (offsets stay relative). Counts as a batch edit (full re-upload) - not a per-frame motion lane
@@ -1491,29 +1561,29 @@ Object.ShowOutlineHighlight(Player: Object, ThicknessPixels: Number, Color: Vect
1491
1561
  Object.HideOutlineHighlight(Player: Object) // Removes this player's outline on caller (no-op if absent)
1492
1562
  Object.EnableLocalSelectionBox(BorderColor: Vector, FillColor: Vector, FillOpacity: Number, BorderWidthPixels: Number, MinDragPixels: Number) // PlayerObject only. Enables a visual-only local drag box for this player; gameplay selection should use Event.MouseDownPosition/Event.MousePosition
1493
1563
  Object.DisableLocalSelectionBox() // PlayerObject only. Disables the visual-only local drag box
1494
- Object.EnableLocalWeaponView(Offset: Position, RotationDegrees: Vector, Scale: Number) // PlayerObject only. Renders this player's equipped weapon as a local first-person camera-attached visual for the owning client (active only in first-person camera). Offset is camera-local cm (+X right, +Y up, +Z forward); RotationDegrees is a first-person camera tilt, (0,0,0) = barrel forward (third person seats the weapon on the hand frame + grip anchor, not on this value); e.g. (Position(15, -25, 32), Vector(2, 4, 0), 1.0) for an FPS-style close lower-right framing (a close-in gun keeps the auto-arms shoulder area off-screen). Visual-only; gameplay still uses EquipWeapon/GetHeldWeapon.
1564
+ Object.EnableLocalWeaponView(Offset: Position, RotationDegrees: Vector, Scale: Number) // PlayerObject only. Renders this player's equipped weapon as a local first-person camera-attached visual for the owning client (active only in first-person camera). Offset is camera-local cm (+X right, +Y up, +Z forward); RotationDegrees is a first-person camera tilt of weapon+arms together, (0,0,0) = barrel forward (hold pose comes from the grip anchor in BOTH first and third person - weapon lying flat/wrong in the hand = fix via SetModelAnchor, not this value); e.g. (Position(15, -25, 32), Vector(2, 4, 0), 1.0) for an FPS-style close lower-right framing (a close-in gun keeps the auto-arms shoulder area off-screen). Visual-only; gameplay still uses EquipWeapon/GetHeldWeapon. Re-callable every frame (whole-config overwrite, cheap): drive Offset/RotationDegrees from Update for sway/bob/recoil kick.
1495
1565
  Object.DisableLocalWeaponView() // PlayerObject only. Disables the local first-person weapon visual
1496
1566
  Object.GetWeaponAnchorWorldDir(AnchorName: Text) -> Vector // Anchor local +X (forward) transformed to world space
1497
1567
  Object.GetWeaponAnchorWorldUp(AnchorName: Text) -> Vector // Anchor local +Z transformed to world space
1498
1568
  Object.GetWeaponAnchorWorldLeft(AnchorName: Text) -> Vector // Anchor local +Y (sim-left) transformed to world space
1499
1569
  Object.GetWeaponAnchorWorldRight(AnchorName: Text) -> Vector // Negative anchor local +Y transformed to world space
1500
- TileGridCreate(TilesX: Number, TilesY: Number, DefaultWalkable: Bool, EdgeBlockingEnabled: Bool) -> List
1570
+ TileGridCreate(TilesX: Number, TilesY: Number, OriginTile: Vector, DefaultWalkable: Bool, EdgeBlockingEnabled: Bool) -> List
1501
1571
  TileGridSetEdgeBlocked(Grid: List, X1: Number, Y1: Number, X2: Number, Y2: Number, Blocked: Bool)
1502
1572
  TileGridIsEdgeBlocked(Grid: List, X1: Number, Y1: Number, X2: Number, Y2: Number) -> Bool
1503
1573
  Object.GetCenterPosition() -> Position
1504
1574
  Object.SetCenterPosition(Position: Position) // Center-anchored twin of SetPosition (same continuous moved semantics). The center resolves against the object's CURRENT rotation, so rotate first, then place (reversed order drifts rotated parts by half a box). On UI/canvas elements both calls place the element's TOP-LEFT (pivot 0,0); call SetUiPivot(1,1) once to center — GetCenterPosition mirrors the same convention
1505
1575
  Object.SetUiFollowsCursor(IsOn: Bool, OffsetX: Number, OffsetY: Number) // UI element on an owned screen canvas. Engine pins element+children at the owner's cursor + offset every frame, clamped on-screen, drawn latency-free. For tooltips/drag images, not clickable elements
1506
1576
  Object.SetRenderVisibleOnlyToPlayerIndex(PlayerIndex: Number) // World object render/pick visibility only for this player index; also sets owner metadata
1507
- Object.SetRenderVisibleToAllPlayers() // Clears private render/pick visibility; does not clear owner metadata
1508
- Object.PlayUpperBodyAnimation(Animation: Object) // Layer a second clip on the upper-body bones while the base clip keeps the legs (wave over walk). The split is by standard bone names (Torso/Head/UpperArm*/Forearm* up; Root/Thigh*/Shin* down), and the layer drives ALL upper bones - unkeyed ones hold rest, so key every upper bone that should move. Blend with SetUpperBodyAnimationWeight
1509
- Object.PlayUpperBodyAnimationOnRepeat(Animation: Object) // Layer a second clip on the upper-body bones while the base clip keeps the legs (wave over walk). The split is by standard bone names (Torso/Head/UpperArm*/Forearm* up; Root/Thigh*/Shin* down), and the layer drives ALL upper bones - unkeyed ones hold rest, so key every upper bone that should move. Blend with SetUpperBodyAnimationWeight
1577
+ Object.SetRenderVisibleToAllPlayers() // Clears private render/pick visibility (only-to AND hidden-for modes); does not clear owner metadata
1578
+ Object.PlayUpperBodyAnimation(Animation: Object) // Layer a second clip on the upper-body bones while the base clip keeps the legs (wave over walk). Upper = the first spine bone above the hips and everything under it (torso, head, arms, hands, props parented there); root and legs stay with the base clip; rigs without a spine bone split by standard names. The layer drives ALL upper bones - unkeyed ones hold rest, so key every upper bone that should move. Blend with SetUpperBodyAnimationWeight
1579
+ Object.PlayUpperBodyAnimationOnRepeat(Animation: Object) // Layer a second clip on the upper-body bones while the base clip keeps the legs (wave over walk). Upper = the first spine bone above the hips and everything under it (torso, head, arms, hands, props parented there); root and legs stay with the base clip; rigs without a spine bone split by standard names. The layer drives ALL upper bones - unkeyed ones hold rest, so key every upper bone that should move. Blend with SetUpperBodyAnimationWeight
1510
1580
  Object.StopUpperBodyAnimation() // Base clip retakes the upper bones instantly; ease SetUpperBodyAnimationWeight to 0 first for a smooth release
1511
1581
  Object.SetCursorImage(ImageAsset: Object)
1512
1582
  Object.SetCursorImage(ImageAsset: Object, HotspotX: Number, HotspotY: Number, Opacity: Number)
1513
1583
  Object.SetWeaponAimCalibration(IsOn: Bool) // Default true: while OrientingWithCameraYaw, the upper body twists so the muzzle (in the weapon-hold pose) aligns with the camera/crosshair ray (aim offset); locomotion bob passes through. Turn off for reloads/cinematic poses where the gun shouldn't track the crosshair.
1514
1584
  Object.SetCursorAim(IsOn: Bool) // Cursor-aim games (top-down/twin-stick): the player's rendered body yaw tracks their live cursor between sim frames, hiding input latency on their own screen; the sim rotation your per-frame LookAt sets stays authoritative. Muzzle/body trails a fast-moving mouse -> turn this on.
1515
1585
  Object.SetCameraFixed(Position: Position, LookAt: Position) // Parks the camera at Position looking at LookAt until a camera-mode switch releases it: persistent across frames AND save/reload, camera collision off while fixed. Security cams, lobby shots, cinematics - no per-frame loop needed.
1516
- SpawnAIPlayer(Name: Text) -> Object // Spawns a bot: a real PlayerObject ('#class player' code, physics, animations) driven by script instead of a user
1586
+ SpawnAIPlayer(Name: Text) -> Object // Spawns a bot: a real PlayerObject ('#class player' code, physics, animations) driven by script instead of a user. Hard cap 64 players incl. bots (SpawnAIPlayer errors at the cap)
1517
1587
  Object.IsAIPlayer() -> Bool // True only for players spawned via SpawnAIPlayer()
1518
1588
  Object.IsConnected() -> Bool
1519
1589
  Object.SetAIMoveVector(Direction: Vector) // Bot move input, like a held stick (persists until changed; xy, auto-clamped to length 1). Takes effect next frame
@@ -1561,7 +1631,7 @@ SetGrassBrightnessVariationPercent(Percent: Number) // Global per-tuft/per-clump
1561
1631
  SetGrassHeightMinCm(HeightCm: Number) // Global grass height in wave troughs, cm (default 20, clamped 0..300). Pair with SetGrassHeightMaxCm; equal values = flat even field. Local average height (soft envelope): per-tuft randomness still spreads around it [global: all clients]
1562
1632
  SetGrassTipColor(Color: Vector) // Built-in grass (cards/blades) tip color, sRGB 0..1. Default #93b944 [global: all clients]
1563
1633
  SetWind(DirectionDeg: Number, Strength01: Number, Gustiness01: Number) // Default: 30, 0.3, 0.5. Renderer-only sway (trees/leaves); strength 0 stills the air [global: all clients]
1564
- TerrainHeightsNoise(Heights: List, SamplesX: Number, SamplesY: Number, Mode: Number, ScaleCells: Number, Octaves: Number, Seed: Number, AmplitudeCm: Number, Gain: Number) // Overwrite Heights with noise (see <terrain_generation>). Mode 0=fbm 1=ridged(mountains) 2=billow(dunes) 3=voronoi(mesas) 4=gradient/perlin(smooth equal-sized rounded hills; zero-mean - via AddNoise it adds SIGNED detail, no mean shift); ScaleCells=feature size; Gain=per-octave amplitude factor, 0=default 0.5 (0.1=near-mono smooth base, 0.95=equal-energy rough detail)
1634
+ TerrainHeightsNoise(Heights: List, SamplesX: Number, SamplesY: Number, Mode: Number, ScaleCells: Number, Octaves: Number, Seed: Number, AmplitudeCm: Number, Gain: Number) // Overwrite Heights with noise (see <terrain_generation>). Mode 0=fbm 1=ridged(mountains) 2=billow(dunes) 3=voronoi(mesas; single octave - Octaves/Gain ignored) 4=gradient/perlin(smooth equal-sized rounded hills; zero-mean - via AddNoise it adds SIGNED detail, no mean shift); ScaleCells=feature size; Gain=per-octave amplitude factor, 0=default 0.5 (0.1=near-mono smooth base, 0.95=equal-energy rough detail)
1565
1635
  TerrainHeightsAddNoise(Heights: List, SamplesX: Number, SamplesY: Number, Mode: Number, ScaleCells: Number, Octaves: Number, Seed: Number, AmplitudeCm: Number, Gain: Number) // Same but adds on top (detail layers)
1566
1636
  TerrainHeightsIslandFalloff(Heights: List, SamplesX: Number, SamplesY: Number, StartRadius01: Number, EdgeNoise01: Number, Seed: Number) // Multiply by radial mask fading to 0 at edge — islands. StartRadius01/EdgeNoise01 are 0..1 fractions (NOT cells; errors outside 0..1). EdgeNoise01 makes the coast ragged (0=circle, ~0.3 natural)
1567
1637
  TerrainHeightsFlattenDisc(Heights: List, SamplesX: Number, SamplesY: Number, CenterXCell: Number, CenterYCell: Number, RadiusCells: Number, HeightCm: Number, BlendCells: Number) // Lerp toward HeightCm: full within RadiusCells of center, smooth falloff over BlendCells — flat build spots
@@ -1617,7 +1687,7 @@ Object.SetSelectedIndex(Index: Number) // Inventory handle only. Select a slot;
1617
1687
  Object.BindInventory(Inv: Object) // Weave2 only. Call on a grid/hStack container: engine spawns one draggable 88x88 slot (icon + count) per inventory slot and keeps them synced every tick (icons/tints/counts from item defs — no RefreshUI). Slot-to-slot drags swap/merge engine-side (InventoryChanged fires); drops outside UI fire InventoryDropOnWorld. Rebinding replaces the old slots
1618
1688
  Object.SetInventorySelectable(IsOn: Bool) // Bound container only (after BindInventory). true = clicking a slot selects it (SetSelectedIndex) and the selected slot is highlighted — hotbar behavior
1619
1689
  CancelInventoryMove() // Weave2 only. Call inside an InventoryDropOnSlot handler to veto the pending engine move (slot filters / equipment slots). No effect elsewhere
1620
- TileGridCreate3D(TilesX: Number, TilesY: Number, Layers: Number, DefaultWalkable: Bool) -> List
1690
+ TileGridCreate3D(TilesX: Number, TilesY: Number, Layers: Number, OriginTile: Vector, DefaultWalkable: Bool) -> List
1621
1691
  TileGridSetWalkable(Grid: List, X: Number, Y: Number, Z: Number, Walkable: Bool)
1622
1692
  TileGridIsWalkable(Grid: List, X: Number, Y: Number, Z: Number) -> Bool
1623
1693
  TileGridSetLinkUp(Grid: List, X: Number, Y: Number, Z: Number, Linked: Bool)
@@ -1630,11 +1700,13 @@ Text.WordCount() -> Number
1630
1700
  Text.ToNumber() -> Number
1631
1701
  Text.Length() -> Number // Character count (chars, not bytes - Thai etc. count per character).
1632
1702
  Text.Substring(Start: Number, Count: Number) -> Text // Chars [Start, Start+Count), 0-based, clamped to the text; negative Start or Count <= 0 -> empty Text.
1703
+ Text.Contains(Needle: Text) -> Bool // True when the text contains Needle (case-sensitive; empty Needle -> true).
1704
+ Text.Replace(From: Text, To: Text) -> Text // Every occurrence of From replaced by To; empty From returns the text unchanged.
1633
1705
  SetOcclusionCulling(Enabled: Bool) // Default: false (experimental Hi-Z occlusion culling)
1634
1706
  TerrainHeightsErosionFilter(Heights: List, SamplesX: Number, SamplesY: Number, CellCm: Number, ScaleCells: Number, Octaves: Number, StrengthCm: Number, Seed: Number, Mask: List, RidgeMapOut: List) // Stateless erosion filter (Phacelle gullies, no droplet sim): slope-aligned V-gullies + dendritic ridges; altitude fade keeps peaks sharp and valley floors intact. ScaleCells=largest gully size, StrengthCm=max carve/raise, Octaves 1..8, Mask=0..1 weights ([]=all), RidgeMapOut (pass [] List, auto-sized) 0..1: 0=crease 1=ridge (splat/tree/drainage rules). The go-to eroded-mountain look. Heavy: cost ~ sx*sy*Octaves (513 grid + 5 octaves = noticeable load time; prefer <=257 grids or <=4 octaves when loads must stay snappy)
1635
1707
  SetTerrainErosionDetailPercent(Percent: Number) // Global display-only knob: scales the GPU sub-cell erosion-gully detail on all terrain surfaces, and the displayed gully depth of #terrain recipe terrains (100 = default, clamped 0..300, 0 = smooth). Visual only — collision/heights unchanged; grass follows the displayed surface automatically [global: all clients]
1636
1708
  TerrainHeightsOriErosion(Heights: List, SamplesX: Number, SamplesY: Number, CellCm: Number, BaseScaleCells: Number, ErosionScaleCells: Number, Octaves: Number, StrengthCm: Number, SlopeStrength: Number, BranchStrength: Number, Seed: Number, Mask: List, ErosionMapOut: List) // OriErosion directional erosion (the #terrain recipe engine as a bulk op; docs/ori_erosion_spec.md): cos-wave gullies steered by the base slope with per-octave branch feedback. BaseScaleCells = base-noise feature size (for slope direction recovery), ErosionScaleCells = first-octave gully size, SlopeStrength ~3, BranchStrength ~0.5, StrengthCm = carve depth. Mask scales accumulation ([] = full); note masked zones sink ~0.5*StrengthCm (water datum). ErosionMapOut (pass [] List, auto-sized) = hx*0.5+0.5 for splat user-plane coloring
1637
- SetTerrainDisplayExtension(RadiusM: Number) // Display-only: renders procedural terrain BEYOND the sim grid out to RadiusM (0 = off), no collision. The generator params come from the world's #terrain block (required; errors without one) — build the sim grid with TerrainHeightsFromTerrain from the same block and the extension continues it seamlessly. Pair with SetCameraFarDistanceM to actually see it
1709
+ SetTerrainDisplayExtension(RadiusM: Number) // Optional render-distance override for recipe terrains (SpawnTerrainRecipe defaults to 8 km; DATA terrains built with TerrainHeightsFromTerrain render the extension beyond their grid, display-only). RadiusM 0 = off. Pair with SetCameraFarDistanceM to actually see far terrain
1638
1710
  SetCameraFarDistanceM(Meters: Number) // Display-only camera far plane in meters; 0 = engine default 1000, else clamped 1000..8000. Distant terrain needs SetFogEndDistance raised too; light clustering keeps near-field tuning; static geometry keeps sun shadows to ~2km (cached far layer; desktop)
1639
1711
  Object.SpawnDamageNumber(Amount: Number) // Damage number above caller: pop-arc motion, world-scaled, size grows with Amount; rounds to integer. Call from WasAttacked with Event.Damage. For custom look use the 5-arg SpawnFloatingText overload
1640
1712
  Object.SpawnFloatingText(Text: Text, Color: Vector, SizeScale: Number, Motion: Number, Anchor: Number) // Styled overload: SizeScale multiplies text size; Motion 0=RiseFade 1=PopArc 2=Sticky 3=BounceLand; Anchor 0=constant screen size 1=scales with camera distance
@@ -1642,7 +1714,7 @@ SetDefaultFloatingTextFont(Font: Object) // World default font for SpawnFloating
1642
1714
  SetWaterEnabled(Enabled: Bool, SeaLevelCm: Number) // THE main water: global displaced ocean surface (Gerstner waves) at SeaLevelCm height. One per world; use Object.SetIsWater meshes for extra pools/streams at other heights (still by default; Object.SetWaterFlow makes streams/falls). Default: off. Camera below the ocean or a flat SetIsWater top gets auto underwater murk (tint follows SetWaterShallowColor/DeepColor) [global: all clients]
1643
1715
  SetWaterWaves(AmplitudeCm: Number, WavelengthCm: Number, Steepness01: Number, DirectionDeg: Number, SpeedPercent: Number) // Ocean swell. AmplitudeCm = total wave height budget, WavelengthCm = dominant wavelength, Steepness01 = chop (near 1 = sharp crests), DirectionDeg = travel direction, SpeedPercent = phase speed (100 = physical dispersion). Defaults: 60, 4000, 0.65, 0, 100 [global: all clients]
1644
1716
  SetWaterColors(ShallowRGB: Vector, DeepRGB: Vector, MixRangeCm: Number, Transparency01: Number) // Ocean tints. MixRangeCm = water depth over which ShallowRGB blends to DeepRGB, Transparency01 = refraction mix. Defaults: (0.18,0.56,0.53), (0.04,0.16,0.24), 300, 0.9 [global: all clients]
1645
- TerrainHeightsFromTerrain(Heights: List, RidgesOut: List, SamplesX: Number, SamplesY: Number, CellSizeCm: Number) // Fill Heights from the world's #terrain recipe block (gradient base -> height mask -> erosion; errors without a #terrain block): the one-call way to build a terrain whose SetTerrainDisplayExtension continuation is exactly seamless. RidgesOut (pass [] List, auto-sized) = 0..1 erosion ridge map for splat user-plane coloring. CellSizeCm must match the SpawnTerrainFromHeights cell size (the recipe's cm values are absolute)
1717
+ TerrainHeightsFromTerrain(Heights: List, RidgesOut: List, SamplesX: Number, SamplesY: Number, CellSizeCm: Number) // Bake the world's #terrain recipe block (recipe + modifier rows; runtime digs excluded) into a bounded Heights grid (gradient base -> height mask -> erosion; errors without a block) - for hand-editing the result with TerrainHeights* ops before SpawnTerrainFromHeights. For plain recipe ground prefer SpawnTerrainRecipe (no bake). RidgesOut (pass [] List, auto-sized) = 0..1 erosion ridge map for splat user-plane coloring. CellSizeCm must match the SpawnTerrainFromHeights cell size (the recipe's cm values are absolute)
1646
1718
  Object.SetTerrainGrassSpawnMaterial(Material: Object) // Terrain only; Material: material with a #shader - a CUSTOM grass spawn COMPUTE shader replacing the default density logic (writes GrassInstance directly; can call ori_terrain_display_height for the rendered surface incl. recipe terrains). None clears. For procedural per-point density (e.g. recipe worlds where a baked weightmap can't extend)
1647
1719
  Object.SetTerrainSurfaces(SurfacesName: Text) // Terrain only; SurfacesName: #terrain_surfaces block name (up to 8 tiled PBR surfaces packed into texture arrays; lines `surface = <albedo>, <normal>, <tiling_m>[, <orm>[, <grass01>]]` - optional orm alpha carries height for crisp height-blended transitions; optional grass01 = grass density factor on that surface (default 1 for the first surface, 0 for the rest, so grass skips rock/snow); pair with SetTerrainSurfaceWeightsMaterial for per-texel weights). "" clears
1648
1720
  Object.SetTerrainSurfaceWeightsMaterial(Material: Object) // Terrain only; Material: material with a #shader - a CUSTOM surface-weights COMPUTE shader writing 8 blend weights per terrain texel (recipe terrains; covers the display extension via the surface clipmap). None clears
@@ -1655,8 +1727,8 @@ Object.SetCharacterOverlapIgnoreTag(Tag: Number, IsOn: Bool) // This character's
1655
1727
  Object.SetDefaultPlayerControlsEnabled(IsOn: Bool) // Weave2 only, player receiver. true: attach the built-in default player controls (WASD camera-relative move + jump) alongside this class — the one-liner for '#class player' hooks that don't code their own movement. false: detach them (freeze for cutscenes; classless-world players can be frozen from any class). Idempotent
1656
1728
  Object.AttachTo(Parent: Object) // Rigidly ride Parent from the CURRENT relative pose (re-baked from Parent's post-physics transform every tick; overrides this object's own moves). Child must be collision-free: display/effect/light/marker objects, or SetCanCollide(false) first. Chains allowed (arm -> gun); cycles error. Replaces per-frame SetPosition follow loops. Order-sensitive in OnSpawned (parent may not be placed yet) - prefer the 3-arg overload there
1657
1729
  Object.AttachTo(Parent: Object, LocalPosition: Position, LocalRotation: Vector) // Explicit-mount overload: LocalPosition (absolute cm) and LocalRotation (euler degrees) in Parent's local frame; safe in OnSpawned. The frame runs CENTER-to-CENTER (not the bottom-center pivot GetPosition/#mesh use): seat a child by adding half ITS height to the offset. Offsets don't rescale when Parent's size changes - re-attach after growth
1658
- Object.AttachToAnchor(Parent: Object, AnchorName: Text) // AttachTo at a named model anchor of Parent's mesh (e.g. "muzzle", "grip"); errors when the anchor doesn't exist (anchors resolve after asset import). The anchor's axis is its local +Z but object/effect forward is local +X - directional effects on an anchor need a LocalRotation (usually RotY +-90). The mount point follows the parent's scale at attach time (where the socket visually sits); captured offsets don't re-follow LATER parent rescales. A `bone:`-bound socket anchor makes the child visually ride that bone's animation (sim pose stays rest)
1659
- Object.Detach() // Remove this object's attachment; it keeps its current world transform
1730
+ Object.AttachToAnchor(Parent: Object, AnchorName: Text) // AttachTo at a named model anchor of Parent's mesh (e.g. "muzzle", "grip"); an anchor missing on Parent's own mesh falls back to the EQUIPPED weapon's anchors (child then follows the held weapon, FP viewmodel included); errors when neither carries it (anchors resolve after asset import). The anchor's axis is its local +Z but object/effect forward is local +X - directional effects on an anchor need a LocalRotation (usually RotY +-90). The mount point follows the parent's scale at attach time (where the socket visually sits); captured offsets don't re-follow LATER parent rescales. A `bone:`-bound socket anchor makes the child visually ride that bone's animation (sim pose stays rest)
1731
+ Object.Detach() // Remove this object's attachment; it keeps its current world transform. Refused for seated players (UnseatPlayer restores their physics)
1660
1732
  Object.GetAttachParent() -> Object // Attachment parent, or None-like non-existing Object when not attached (check with Exists())
1661
1733
  EaseIn(T: Number) -> Number
1662
1734
  EaseOut(T: Number) -> Number
@@ -1675,9 +1747,9 @@ Vector.RotateByEuler(Yaw: Number, Pitch: Number, Roll: Number) -> Vector // Rota
1675
1747
  Vector.ToYawPitch() -> Vector // Treat this vector as a direction: returns Vector(yaw, pitch, 0) in degrees. Yaw 0 = +X (counter-clockwise toward +Y), pitch positive = up. Zero-length input gives (0, 0, 0)
1676
1748
  YawPitchToDirection(Yaw: Number, Pitch: Number) -> Vector // Unit direction from aim angles in degrees (inverse of ToYawPitch): yaw 0 = +X, pitch positive = up
1677
1749
  SetDefaultFont(Font: Object) // World default font for all UI text/buttons that don't set one (e.g. Ori.font.google.Fredoka); None reverts to the engine default (Nunito)
1678
- SetModelAnchor(Model: Object, AnchorName: Text, LocalPosition: Position, LocalRotationEulerDegrees: Vector) // Weapon sits wrong in the hand / anchor misplaced -> define or replace any named anchor ("grip", "muzzle", custom): model-root local, cm Z-up, rotation maps anchor axes (+X = forward) into model axes; wins over the asset's own anchors, call before or after EquipWeapon; a grip anchor seats BOTH the third-person hold and the EnableLocalWeaponView viewmodel
1679
- SwitchStage(Name: Text) // `#stage` projects only: switch to another stage of this project (multiplayer-safe; all stages ship in the project source, so Name may be dynamic). Use LoadWorldTransferPush before switching to carry values across.
1680
- Object.SetRenderOffset(OffsetCm: Vector, RotationEulerDegrees: Vector, Scale: Number) // World/display/player objects only (crowds/instance batches reject). Visual-only offset on top of the sim transform (bob/lean/sway); colliders/AI/queries unaffected. OffsetCm shifts in WORLD space (not rotated into the object's local frame); rotation composes after the object's own and pivots at the object CENTER (SetRotation pivots at origin = ref-box bottom-center). Call per-frame for procedural animation; ClearRenderOffset resets
1750
+ SetModelAnchor(Model: Object, AnchorName: Text, LocalPosition: Position, LocalRotationEulerDegrees: Vector) // Weapon sits wrong in the hand / anchor misplaced -> define or replace any named anchor ("grip", "muzzle", custom): model-root local, cm Z-up, rotation maps anchor axes (+X = forward) into model axes; wins over the asset's own anchors, call before or after EquipWeapon; a grip anchor seats BOTH the third-person hold and the EnableLocalWeaponView viewmodel; e.g. handle authored along +Z -> ("grip", <grip point>, Vector(0,-90,0))
1751
+ SwitchStage(Name: Text) // `#stage` projects only: switch to another stage of this project (multiplayer-safe; all stages ship in the project source, so Name may be dynamic). Carry values with LoadWorldTransferClear+Push first; see <multi_stage_projects>.
1752
+ Object.SetRenderOffset(OffsetCm: Vector, RotationEulerDegrees: Vector, Scale: Number) // World/display/player objects only (crowds/instance batches reject). Visual-only offset on top of the sim transform (attack lunges, hit reactions, bob/lean/sway); colliders/AI/queries unaffected. OffsetCm shifts in WORLD space (not rotated into the object's local frame); rotation composes after the object's own and pivots at the object CENTER (SetRotation pivots at origin = ref-box bottom-center). Call per-frame for procedural animation; ClearRenderOffset resets
1681
1753
  Object.ClearRenderOffset() // Remove this object's visual render offset
1682
1754
  Object.RaycastObject(Origin: Position, Direction: Vector, MaxDistance: Number) -> Object // Nearest object the ray hits within MaxDistance (this object excluded), or a None-like Object on miss (check Exists()). Hits anything solid incl. untagged walls/floor/terrain - the go-to for projectile impact and line-of-sight tests. Direction need not be normalized; fast projectiles: raycast the segment travelled this frame (origin = previous position, MaxDistance = the step) - positional overlap checks skip thin targets between frames
1683
1755
  Object.RaycastObjectWithTag(Origin: Position, Direction: Vector, MaxDistance: Number, Tag: Number) -> Object // RaycastObject limited to objects carrying Tag
@@ -1689,19 +1761,19 @@ PlaySoundAtPosition(Sound: Object, Position: Position, Volume: Number, HearingRa
1689
1761
  TileGridHasLineOfSight(Grid: List, StartX: Number, StartY: Number, GoalX: Number, GoalY: Number) -> Bool
1690
1762
  TileGridHasLineOfSight(Grid: List, StartX: Number, StartY: Number, StartZ: Number, GoalX: Number, GoalY: Number, GoalZ: Number) -> Bool
1691
1763
  TileGridSmoothPath(Grid: List, Path: List) -> List
1692
- Object.SetTerrainGrassVisible(Visible: Bool) // Terrain only; false = no grass on this terrain (deserts/cities/moon), true = grass even while the terrain object is hidden, and counts as grass intent (enables the auto ground tint = full meadow look). Unset default: sparse untinted grass following terrain visibility. Same as declarative grass_visible
1764
+ Object.SetTerrainGrassVisible(Visible: Bool) // Terrain only; false = no grass on this terrain (deserts/cities/moon), true = grass even while the terrain object is hidden. Unset default: sparse grass following terrain visibility. Same as declarative grass_visible. Bare ground shows between tufts: for a meadow read, keep the terrain material/splat in the grass root-color family
1693
1765
  SpawnEffectAimed(Effect: Object, Position: Position, Direction: Vector) -> Object // Spawns with the effect's authored up/emission axis aimed along Direction (e.g. exhaust: SpawnEffectAimed(fx, GetPosition(), -GetFacingDirection())). Zero direction = unrotated.
1694
1766
  Object.SetEmitterLightsEnabled(Enabled: Bool) // Toggle the lights this object's mesh bakes via emitter(...) sockets (default on) - e.g. lamps dark at day
1695
1767
  Object.GetEmitterLightsEnabled() -> Bool
1696
1768
  SetFogSkyboxAffect(Percent: Number) // Default: 0 (sky ignores distance fog). 100 = distant sky fully fogged, ~30 = horizon haze [global: all clients]
1697
- TileGridSetWalkableFromStaticColliders(Grid: List, OriginTile: Vector, ZMin: Number, ZMax: Number)
1769
+ TileGridSetWalkableFromStaticColliders(Grid: List, ZMin: Number, ZMax: Number)
1698
1770
  Object.SetCursorUiMode(On: Bool) // true: release mouse-look AND show the cursor so players can click UI (menus/shops); false: restore gameplay look. Prefer over toggling SetIsMouseLookOn/SetMouseVisible separately.
1699
- Object.SetBoneRotation(BoneName: Text, EulerDegrees: Vector) // Replace the named bone(...) rig joint's local rotation (Euler degrees Z-up, #clip convention); wins over the playing clip for that bone until cleared. Max 16 bones per object. Display-only: colliders/physics unaffected. Call per-frame in forever{} for tails, breathing, head tracking
1771
+ Object.SetBoneRotation(BoneName: Text, EulerDegrees: Vector) // Replace the named bone(...) rig joint's local rotation (Euler degrees in the bone's local rest frame, #clip convention); wins over the playing clip for that bone until cleared. Max 16 bones per object. Display-only: colliders/physics unaffected. Call per-frame in forever{} for tails, breathing, head tracking. BoneName is the rig's exact bone name (inspect_rig lists them; Mixamo rigs carry the mixamorig: prefix) - a miss is a no-op reported in render warnings
1700
1772
  Object.ClearBoneOverrides() // Remove all of this object's bone rotation overrides (SetBoneRotation, Mix, and BoneLookAt)
1701
1773
  Object.SetBoneRotationMix(BoneName: Text, EulerDegrees: Vector, Weight01: Number, BlendSeconds: Number, Additive01: Number) // Weighted, smoothed bone layer over the playing clip: Weight01 blends toward EulerDegrees, BlendSeconds eases changes, Additive01=1 offsets the clip pose instead of replacing it. On a rig with no clip playing it animates from rest - the fully procedural mode. Max 16 bones per object; display-only
1702
1774
  Object.BoneLookAt(BoneName: Text, WorldTarget: Vector, MaxDegrees: Number, DegreesPerSecond: Number) // Aim the named bone at a world point each frame (engine-solved, clamped to MaxDegrees from rest; DegreesPerSecond=0 snaps). Call again to retarget; ClearBoneOverrides releases. Head tracking and aiming without per-frame math
1703
1775
  Object.SetGroundConform(Enabled01: Number) // Skinned characters ease their render z so the lowest reaching foot meets the ground under them (slopes, steps), and rigs with knee joints bend each leg so both feet meet uneven ground; flat ground is a no-op and airborne characters disengage. Display-only: colliders and sim position unaffected
1704
- Object.SetUpperBodyAnimationWeight(Weight01: Number, BlendSeconds: Number) // How strongly the PlayUpperBodyAnimation layer drives the upper-body bones: 1 full replace (the default), 0 base clip only; BlendSeconds eases changes (0 snaps). Fade an upper action in/out over the base walk/run. Sticky per object; ClearBoneOverrides resets. Display-only
1776
+ Object.SetUpperBodyAnimationWeight(Weight01: Number, BlendSeconds: Number) // How strongly the upper-body layer (PlayUpperBodyAnimation, a held weapon's hold-idle) drives the upper-body bones: 1 full replace (the default), 0 base clip only; BlendSeconds eases changes (0 snaps). Fade an upper action in/out over the base walk/run. Sticky per object; ClearBoneOverrides resets. Display-only
1705
1777
  AnyBasicType.NearestObjectWithTagNotOnTeam(Tag: Number, TeamId: Number, MaxDistance: Number) -> Object // Nearest Tag-carrier whose team != TeamId within MaxDistance cm; null Object when none (check .Exists()). The cheap per-tick targeting query (never returns Self)
1706
1778
  AnyBasicType.NearestObjectXYWithTagNotOnTeam(Tag: Number, TeamId: Number, MaxDistance: Number) -> Object // Same but horizontal (XY) distance — top-down games
1707
1779
  AnyBasicType.NearestObjectWithTagOnTeam(Tag: Number, TeamId: Number, MaxDistance: Number) -> Object
@@ -1716,7 +1788,7 @@ Object.GetAnchorWorldDir(AnchorName: Text) -> Vector // Anchor local +X (forward
1716
1788
  Object.GetAnchorWorldUp(AnchorName: Text) -> Vector // Anchor local +Z (the socket's declared axis:) rotated to world space; unit vector, unaffected by object scale.
1717
1789
  IsWorldReady() -> Bool // true once the current load's scene OnSpawned batch has fully started (see event OnWorldReady); false again from the start of a new load/restart. Poll it before placing things on procedurally built ground/rooms.
1718
1790
  SetShadowDistance(DistanceMeters: Number) // Crisp sun-shadow range in meters; past it only static geometry casts (coarse cached shadows to ~2km, desktop). Default: 50 (character scale); building-onto-building / city blocks need 200-300. Cost: shadow edges get chunkier as range grows [global: all clients]
1719
- SetDebugRenderMode(Mode: Number) // Diagnostic view: renders ONE lighting/material component ungraded instead of the shaded frame, to tell wrong albedo from wrong light from wrong geometry. 0 off, 1 albedo, 2 normal, 3 material ao, 4 ssao, 5 ao*ssao (what ambient is multiplied by - 0 means no ambient can reach the surface), 6 direct sun, 7 ambient/sky, 8 shadow mask, 9 roughness, 10 metallic, 11 emissive, 12 local lights. Ship worlds with 0; the playtest/live_screenshot `debug_view` parameter is the per-capture equivalent. Default: 0
1791
+ SetDebugRenderMode(Mode: Number) // Diagnostic view: renders ONE lighting/material component ungraded instead of the shaded frame, to tell wrong albedo from wrong light from wrong geometry. 0 off, 1 albedo, 2 normal, 3 material ao, 4 ssao, 5 ao*ssao (what ambient is multiplied by - 0 means no ambient can reach the surface), 6 direct sun, 7 ambient/sky, 8 shadow mask, 9 roughness, 10 metallic, 11 emissive, 12 local lights, 13 wireframe (scene-mesh line overlay over the normal frame; desktop only). Ship worlds with 0; the playtest/live_screenshot `debug_view` parameter is the per-capture equivalent. Default: 0
1720
1792
  PersistClear()
1721
1793
  Object.SetInstance(Index: Number, Offset: Vector, RotationEuler: Vector, Size: Vector, ColorRGB: Vector) // Batch only. Overwrites the instance at Index (0..GetInstanceCount-1); args like AddInstance. Errors on out-of-range Index
1722
1794
  Object.RemoveInstance(Index: Number) // Batch only. Swap-remove: the LAST instance moves into Index, count shrinks by 1. Errors on out-of-range Index
@@ -1725,10 +1797,10 @@ GetRayHitDistance(Position: Position, Direction: Vector, Tag: Number) -> Number
1725
1797
  GetRayHitDistance(Position: Position, Direction: Vector) -> Number // Unfiltered. Distance in cm to the first hit, -1 on miss — range gates and damage falloff; prefer this over measuring to GetRayHitPosition, whose miss sentinel is Position + Direction * 1000000.
1726
1798
  SetSkyboxCloudType(Type: Number) // Procedural sky only. Cloud vertical form. Default: 0.35; 0 = flat haze layer, 1 = towering cumulus
1727
1799
  CreateStaticInstanceBatch(Model: Object, Origin: Position) -> Object // Display-only STATIC scatter batch (see <procedural_generation>); Model: Ori.model.* (basics like Ori.model.cone work) or Ori.alias.model.* - mesh models ONLY (effects/images/spawned Objects are rejected at runtime). Max 128 live per world - Destroy() frees. Never mutate per frame - each edit (SetBatchOrigin included) re-uploads the whole batch. Sway/shimmer: vertex #shader on the model's material; instances that truly move: objects or #particles
1728
- GetRayHitPart(Position: Position, Direction: Vector, Tag: Number) -> Text // Tag filters to objects WITH that tag; 0 is a real tag index, NOT "no filter" — use the 2-arg overload for unfiltered rays. Names the hit volume posed by the target's current animation: "head"/"torso"/"pelvis"/"upper_arm_l"/"forearm_r"/"thigh_l"/"shin_r"/...; "" on miss, rigless target, or a hit outside every volume (treat as a plain body hit) — gate damage on GetRayHitObject, use the part for multipliers.
1800
+ GetRayHitPart(Position: Position, Direction: Vector, Tag: Number) -> Text // Tag filters to objects WITH that tag; 0 is a real tag index, NOT "no filter" — use the 2-arg overload for unfiltered rays. Names the hit volume posed by the target's current animation: "head"/"torso"/"pelvis"/"upper_arm_l"/"forearm_r"/"thigh_l"/"shin_r"/...; "" on miss, rigless target, or a hit outside every volume (treat as a plain body hit) — gate damage on GetRayHitObject, use the part for multipliers. The caster's own body never blocks or names its rays (whole GetRayHit* family, like RaycastObject).
1729
1801
  GetRayHitPart(Position: Position, Direction: Vector) -> Text // Unfiltered twin: same part names and "" semantics as GetRayHitPart.
1730
1802
  TileGridFlowFieldBuild(Grid: List, GoalTiles: List) -> List
1731
- Object.StartTileFlowFollow(Field: List, Grid: List, OriginTile: Vector, Speed: Number) -> Bool
1803
+ Object.StartTileFlowFollowStepped(Field: List, Grid: List, Speed: Number) -> Bool
1732
1804
  TilePathFollowTakeBlocked() -> List
1733
1805
  Object.SetSubmeshTint(SubmeshIndex: Number, TintRGB: Vector) // THIS object's per-submesh tint (faction colors): same LOD-flattened slot space as SetAssetSubmeshTint; multiplies SetColor and the asset tint (black kills). Tint (1,1,1) clears the slot. Skinned models fully supported; tinted statics leave the LOD-collapse fast path; InstanceBatch/TileMap handles reject
1734
1806
  SetFogOfWarEnabled(Enabled: Bool) // RTS fog of war: per-TEAM vision grid sized from the largest terrain (runtime error without one). Each viewer sees own-team vision; unexplored = black, explored-but-unseen = grey memory (objects hidden), visible = clear. Radius discs only (no line-of-sight blocking); audio is not gated. Enable once at world start; false clears everything. No default sight: until Object.SetSightRadius lands on TEAMED objects, teamed viewers see a near-black stage - grant sight in the same change as the enable. Global (one grid, all players); max 8 distinct sighted teams, later teams see nothing. Per-PLAYER vision: SetTeam(GetPlayerIndex() + 1) on each player + their owned objects, shared objects stay teamless; SetSightRadius(100000) = that player sees everything
@@ -1738,7 +1810,7 @@ GetTerrainBoundsMin() -> Position // World-space min corner of the LARGEST terra
1738
1810
  GetTerrainBoundsMax() -> Position // World-space max corner of the LARGEST terrain (z = highest height sample). Minimap math: uv = (P - Min) / (Max - Min).
1739
1811
  GetRayHitNormal(Position: Position, Direction: Vector, Tag: Number) -> Vector // Tag filters to objects WITH that tag; 0 is a real tag index, NOT "no filter" — use the 2-arg overload for unfiltered rays. Unit surface normal at the hit (world axes; box = face normal, sphere = radial); zero Vector on miss.
1740
1812
  GetRayHitNormal(Position: Position, Direction: Vector) -> Vector // Unfiltered twin: same normal and zero-Vector miss semantics as GetRayHitNormal.
1741
- Object.SetSavedToWorld(IsOn: Bool) // Script-spawned objects are TRANSIENT: saving the world never writes them into the scene text (your Spawn code recreates them). SetSavedToWorld(true) makes this one save like an editor-placed object; editor-placed objects always save
1813
+ Object.SetSavedToWorld(IsOn: Bool) // Script-spawned objects are TRANSIENT: saving the world never writes them into the scene text (your Spawn code recreates them). SetSavedToWorld(true) makes this one save like an editor-placed object; editor-placed objects always save. The saved clause carries the template's declared overrides, not runtime field changes
1742
1814
  ArcToTarget(From: Position, To: Position, FlightSeconds: Number, GravityCmS2: Number) -> List // Arc handle leaving From and passing through To exactly at FlightSeconds under GravityCmS2 (positive cm/s^2, e.g. 980; captured in the handle - world SetGravity is NOT used). Query with ArcPositionAt/ArcVelocityAt/ArcFlightSeconds.
1743
1815
  ArcToTargetWithPeak(From: Position, To: Position, PeakHeightCm: Number, GravityCmS2: Number) -> List // As ArcToTarget but sized by apex height above From (the 'make it float higher' knob); errors when PeakHeightCm can't reach the target height. Read the flight time back with ArcFlightSeconds.
1744
1816
  ArcFlightSeconds(Arc: List) -> Number // Seconds from launch until the arc reaches its target - the contact time to hand to MoveToArriveIn / PlayAnimationHittingContactIn.
@@ -1765,24 +1837,89 @@ SessionPlayerCount(Index: Number) -> Number // Connected player count for a list
1765
1837
  OpenSessionBrowser() // Open the engine's built-in session browser UI (no-op in headless/validation).
1766
1838
  Object.ShatterAndDestroy() // Destroy() plus a debris burst from the model's baked chunk set (needs `#mesh Name destructible: <word>`; without it, plain Destroy()). Debris is display-only - no sim collision.
1767
1839
  TerrainSlopeAtXY(X: Number, Y: Number) -> Number // slope01 (g/(1+g); 0.5 = 45deg) of topmost terrain at world XY; 1 if no terrain (void = unwalkable). Walkers wedge above ~0.5
1768
- TileGridSetWalkableFromTerrain(Grid: List, OriginTile: Vector, MaxSlope01: Number)
1769
- TileGridSetEdgesFromTerrain(Grid: List, OriginTile: Vector, MaxStepCm: Number)
1840
+ TileGridSetWalkableFromTerrain(Grid: List, MaxSlope01: Number)
1841
+ TileGridSetEdgesFromTerrain(Grid: List, MaxStepCm: Number)
1770
1842
  TileGridStampRect(Grid: List, X1: Number, Y1: Number, X2: Number, Y2: Number, Blocked: Bool)
1771
- TileGridStampObject(Grid: List, OriginTile: Vector, Obj: Object, Blocked: Bool)
1843
+ TileGridStampObject(Grid: List, Obj: Object, Blocked: Bool)
1772
1844
  TileGridIsReachable(Grid: List, X1: Number, Y1: Number, X2: Number, Y2: Number) -> Bool
1773
1845
  TileGridIsReachable(Grid: List, X1: Number, Y1: Number, Z1: Number, X2: Number, Y2: Number, Z2: Number) -> Bool
1774
- TileGridBakeCrowdObstacles(Grid: List, OriginTile: Vector)
1846
+ TileGridBakeCrowdObstacles(Grid: List)
1775
1847
  TerrainHeightsFromTiers(Heights: List, SamplesX: Number, SamplesY: Number, Tiers: Text, TierCm: Number) // REPLACE Heights from an ASCII tier map (digits = tier * TierCm, '.' = 0, 'r' = ramp; first row = max Y). See <terrain_generation>
1776
1848
  RaiseWorldError(Message: Text) // Fail-loud assert: raises a Weave runtime error carrying Message (validation surfaces it; in play the program pauses like any script error)
1777
1849
  SetWaterMurkDensity(Density: Number) // Scales underwater-camera murk: 1 = default, 0 = crystal clear (underwater/side-view games), max 4. [global: all clients]
1778
1850
  Object.SetFogDistances(StartM: Number, EndM: Number) // THIS player's display fog distances in meters, replacing the global SetFogStartDistance/SetFogEndDistance on their screen only (night vision, zombie-side sight); EndM 0 or negative clears back to the globals
1851
+ TileGridFindPath4NextTo(Grid: List, StartX: Number, StartY: Number, GoalX: Number, GoalY: Number) -> List
1852
+ TileGridFindPath8NextTo(Grid: List, StartX: Number, StartY: Number, GoalX: Number, GoalY: Number) -> List
1853
+ Object.GetMouseTargetGroundPosition() -> Position // Player only. The ground-order click: cursor ray vs terrain + static colliders (passes through units; terrain ignores fog, fog-hidden objects are skipped); Invalid on miss - gate with .IsValid()
1854
+ Object.SetHoverHPBarSize(Width: Number, Height: Number) // Screen-px size of the caller's hover HP bar (default 80x6); Width or Height <= 0 reverts. Persists across SetHoverHPBar updates; ClearHoverBars removes it
1855
+ Object.SetHoverSPBarSize(Width: Number, Height: Number) // Same as SetHoverHPBarSize for the hover SP bar
1856
+ Object.PossessPlayer(Target: Object) // This player's user inputs + camera drive Target (must be an AI player) until UnpossessPlayer; their own body idles. Vehicle/minion control, spectate, and solo multiplayer QA (drive the other role yourself). AI-player receivers and entering the editor are no-ops/release
1857
+ Object.UnpossessPlayer() // Release PossessPlayer: this player's inputs + camera return to their own body
1858
+ SpawnTerrainRecipe(TileSizeCm: Number, Position: Position) -> Object // Spawns the world's #terrain block as an unbounded collidable recipe terrain (no heights list, no baking; errors without a block). TileSizeCm = collision sample spacing cm; omit it (SpawnTerrainRecipe(Position)) to default to the nav TileSize (100) so terrain samples align 1:1 with nav tiles. Field is centered on Position. One recipe terrain per world
1859
+ SpawnTerrainRecipe(Name: Text, TileSizeCm: Number, Position: Position) -> Object // Same + registers the terrain asset under Name (error if name taken or starting with 'terrain.')
1860
+ TerrainDig(Center: Position, Radius: Number, Depth: Number) -> Number // Digs a smooth pit into the world's RECIPE terrain (cm; full depth inside Radius/2, easing to 0 at Radius). Runtime overlay edit: collision + display + queries update, synced, join/rollback-safe, saved to world text (#terrain_overlay). Returns cells changed (0 = budget exhausted)
1861
+ TerrainFlatten(Center: Position, Radius: Number, Height: Number) -> Number // Levels a disc of the world's RECIPE terrain toward world-Z Height (cm; same shoulder as TerrainDig). Build pads during play; same overlay rules as TerrainDig. Returns cells changed
1862
+ TerrainOverlayClear() -> Number // Removes ALL TerrainDig/TerrainFlatten/Voxel* edits (both overlays), restoring the pristine recipe field. Returns tiles + chunks cleared
1863
+ ClearTransientEffects() // Sweeps lingering visual transients (destruction debris, floating texts, death anims) for scripted soft restarts (PLAY AGAIN) - the in-code partner of RestartGame's full reload.
1864
+ VoxelGet(Position: Position) -> Number // Block id at a world position on the block terrain (0 = air, N = N-th #terrain_surfaces entry). Errors unless the #terrain block has blocks = true
1865
+ VoxelSet(Position: Position, Id: Number) -> Number // Sets one block (0 = air). Needs overhangs = true. Synced; session state (not saved, unlike TerrainDig). Returns blocks changed (0 = no-op or budget exhausted)
1866
+ VoxelFillBox(Min: Position, Max: Position, Id: Number) -> Number // Fills every block in the world-space box with Id (max 64 m per side). Needs overhangs = true. Returns blocks changed
1867
+ VoxelFillSphere(Center: Position, Radius: Number, Id: Number) -> Number // Fills blocks within Radius cm of Center with Id (max 32 m; Id 0 carves). Needs overhangs = true. Returns blocks changed
1868
+ VoxelSetHeight(Position: Position, Height: Number) -> Number // Block terrain: the column under Position becomes solid up to Height cm (block-snapped) and air above; with overhangs only the moved span is overwritten. Returns blocks changed
1869
+ VoxelSetHeightBox(Min: Position, Max: Position, Height: Number) -> Number // VoxelSetHeight for every column in the XY rect Min..Max (Z ignored; max 64 m per side). Returns blocks changed
1870
+ Object.MoveUiToFront() // Moves the element last among its siblings = draws on top (inside a vStack/hStack/grid it also takes the last layout slot). Z-order otherwise stays declaration order
1871
+ Object.SetUiOffset(X: Number, Y: Number, Seconds: Number) // Glides to the target over Seconds (EaseInOut). Same-property call replaces the running glide; the instant 2-arg call cancels it. SetUiSize/SetUiOpacity/SetUiColor have the same overloads
1872
+ Object.SetUiOffset(X: Number, Y: Number, Seconds: Number, Ease: Number) // Ease 0=linear 1=in 2=out 3=in-out 4=out-back 5=out-bounce (offset/size/opacity glides)
1873
+ Object.SetUiSize(Width: Number, Height: Number, Seconds: Number)
1874
+ Object.SetUiSize(Width: Number, Height: Number, Seconds: Number, Ease: Number)
1875
+ Object.SetUiOpacity(Opacity01: Number, Seconds: Number)
1876
+ Object.SetUiOpacity(Opacity01: Number, Seconds: Number, Ease: Number)
1877
+ Object.SetUiColor(Color: Vector, Seconds: Number) // Glides rgb over Seconds (in-out; alpha untouched; no Ease arg - it would collide with the SetUiColor(r, g, b) form)
1878
+ Object.GetUiOffsetX() -> Number // Current offset in base units (mid-glide = the in-flight value); GetUiSizeX/Y same for size
1879
+ Object.GetUiOffsetY() -> Number
1880
+ Object.GetUiSizeX() -> Number
1881
+ Object.GetUiSizeY() -> Number
1882
+ Object.SetUiRotation(Degrees: Number) // Spins the element AND its subtree around the element's rect center (degrees, +CW). Fanned card hands, dials, compasses. Hit-testing follows; clipping stays the screen-AABB scissor. Same glide overloads as SetUiOffset
1883
+ Object.SetUiRotation(Degrees: Number, Seconds: Number)
1884
+ Object.SetUiRotation(Degrees: Number, Seconds: Number, Ease: Number)
1885
+ Object.GetUiRotation() -> Number // Current rotation in degrees (mid-glide = in-flight value)
1886
+ TileGridCreateFromTerrain(TilesX: Number, TilesY: Number, OriginTile: Vector, MaxSlope01: Number, MaxStepCm: Number) -> List
1887
+ TileGridRegionAt(Grid: List, X: Number, Y: Number) -> Number // FromTerrain grids: region id of the tile (0 = unwalkable); equal ids = reachable from each other; ids link only across explored chunks, so settle a different id with TileGridIsReachable
1888
+ Object.SetCameraRollThisFrame(RollDegrees: Number) // One-frame roll of the camera about its view axis (degrees, + = clockwise; call every frame to hold, decays to 0 like the other ThisFrame overrides). Composes with every camera mode. Display-only. NOTE: cursor picking and world-anchored UI stay roll-unaware - off-center clicks skew while rolled; fine for tilt/shake/lean.
1889
+ TileGridFindRoute(Grid: List, StartX: Number, StartY: Number, GoalX: Number, GoalY: Number) -> List // FromTerrain grids, any distance; the List is for StartTilePathFollowStepped only (empty = unreachable)
1890
+ TileGridCreate(TilesX: Number, TilesY: Number, OriginTile: Vector, DefaultWalkable: Bool, EdgeBlockingEnabled: Bool, DefaultCost: Number) -> List
1891
+ TileGridSetCost(Grid: List, X: Number, Y: Number, Cost: Number)
1892
+ TileGridGetCost(Grid: List, X: Number, Y: Number) -> Number
1893
+ TileGridSetCostRect(Grid: List, X1: Number, Y1: Number, X2: Number, Y2: Number, Cost: Number)
1894
+ GetTerrainEditVersion() -> Number // Counter bumped by every TerrainDig/TerrainFlatten/TerrainOverlayClear (and the load-time dig restore); 0 before the recipe terrain spawns. Poll it to re-bake nav lazily; event OnTerrainEdited is the push form
1895
+ TileGridSetZone(Grid: List, X: Number, Y: Number, Zone: Number)
1896
+ TileGridZoneAt(Grid: List, X: Number, Y: Number) // The tile's zone byte (0 = unzoned; set with TileGridSetZone/SetZoneRect) - districts, territory, resource layers. Any 2D grid (dense grids grow the plane on first SetZone; layered 3D rejects)
1897
+ TileGridSetZoneRect(Grid: List, X1: Number, Y1: Number, X2: Number, Y2: Number, Zone: Number)
1898
+ TerrainOverlayTakeChangedRects() -> List // Drains the terrain-edit rects since the last call as flat X1,Y1,X2,Y2 world-cm quads (empty = no edits); pair with OnTerrainEdited and re-bake nav via the FromTerrainRect calls
1899
+ Object.AddCameraPitchImpulse(PitchDegrees: Number, YawDegrees: Number, RecoverySeconds: Number) // Weapon-recoil camera kick that COMPOSES with mouse-look: PitchDegrees up / YawDegrees right land as a view offset the player can counter-steer, decaying to zero over RecoverySeconds; the sim aim ray follows via the camera snapshot. PlayerObject receiver, shown on that player's own client. Use for recoil - SetCameraTiltDown/SetCameraRotation are absolute and HOLD their axis against the mouse for ~250 ms.
1900
+ Object.SetUiSheetFrame(FrameIndex: Number) // Hold sheet frame FrameIndex (0-based row-major); becomes the standing state (stops auto-play). Runtime error without a `sheet` clause or index out of range.
1901
+ Object.SetUiSheetRange(StartFrame: Number, EndFrame: Number) // Standing loop over sheet frames StartFrame..EndFrame inclusive at the clause fps, starting at StartFrame now.
1902
+ Object.SetUiSheetOnce(StartFrame: Number, EndFrame: Number) // Play sheet frames StartFrame..EndFrame once at the clause fps, then return to the standing loop/hold - one-shot attacks/hits without Wait bookkeeping.
1903
+ GetWaterDepthAt(X: Number, Y: Number) -> Number // Water depth in cm at the point (0 = dry)
1904
+ GetWaterSurfaceZAt(X: Number, Y: Number) -> Number // World z of the water surface at the point; equals the ground z when dry. Submerged check: position z below this while GetWaterDepthAt > 0
1905
+ AddWaterRect(X1: Number, Y1: Number, X2: Number, Y2: Number, DepthCm: Number) // Pours DepthCm of water over the rect on the topmost block/recipe terrain; it then FLOWS downhill, levels, and settles on its own (walls and raised ground dam it; tunnels stay dry). Volume is conserved until DrainWaterRect or evaporation removes it; permanent springs/rivers are AddWaterSource's job
1906
+ DrainWaterRect(X1: Number, Y1: Number, X2: Number, Y2: Number, DepthCm: Number) // Removes up to DepthCm from each cell in the rect (clamped at dry)
1907
+ GetWaterAmountRect(X1: Number, Y1: Number, X2: Number, Y2: Number) -> Number // Total water volume in the rect, cubic meters - verify a pour or drain landed
1908
+ SetWaterEvaporation(CmPerSecond: Number) // World evaporation rate in cm of depth per second (default 0 = water is permanent); also clears the thin damp films flowing water leaves behind
1909
+ AddWaterSource(X: Number, Y: Number, CmPerSecond: Number) // Plants a permanent spring at the point: pours CmPerSecond of depth into that cell every tick until removed. Feed a slope for a river, a basin for a filling lake; pair with SetWaterEvaporation so the world reaches a steady state
1910
+ RemoveWaterSourceAt(X: Number, Y: Number) // Removes any springs in the cell at the point
1911
+ SetWaterShader(Material: Object) // Replace ALL water surface shading (placed water, voxel water tiles, ocean) with a #shader material whose body defines the full fn shade_water - fork the stock source (read_shader Ori.shader.builtin.water_default), never write from scratch. None restores engine water. mat.params4 works via SetMaterialParameterNumber; material texture slots not yet. See shader_dsl skill
1912
+ Object.MoveAlongTileFlowContinuous(Field: List, Grid: List, Speed: Number) -> Bool
1913
+ Object.SetRenderHiddenForPlayerIndex(PlayerIndex: Number) // Inverse: render/pick HIDDEN for exactly this player index, visible to everyone else (shared object with a private replacement preview); also sets owner metadata
1914
+ DrawPreviewForPlayer(PlayerIndex: Number, SavedObject: Object, Position: Position, Rotation: Vector) // Same ghost draw, rendered only on PlayerIndex's client (per-player build previews); an index with no live player draws nowhere
1915
+ DrawPreviewForPlayerWithSizeAndColor(PlayerIndex: Number, SavedObject: Object, Position: Position, Rotation: Vector, Size: Vector, Color: Vector) // Same ghost draw, rendered only on PlayerIndex's client (per-player build previews); an index with no live player draws nowhere
1779
1916
 
1780
1917
  </built_in_function_list>
1781
1918
 
1782
1919
  <procedural_generation>
1783
- - Hash2/ValueNoise2/Fbm2/Ridged2/Billow2/Voronoi2 are pure deterministic functions of (X, Y[, Octaves], Seed): same inputs -> same value on every client, every run. Use them (not RandomNumber) for stable layouts: terrain, village/tree placement, scatter.
1920
+ - Hash2/ValueNoise2/Fbm2/Ridged2/Billow2/Voronoi2 are pure deterministic functions of (X, Y[, Octaves], Seed): same inputs -> same value on every client, every run. Use them (not RandomNumber) for stable layouts: terrain, village/tree placement, scatter. For per-run variety (roguelike maps/decks) mix a session seed (TimeTicks() at first input - see Determinism caveats) into Hash2 draws; don't port textbook integer PRNGs - LCG constants overflow 48.16 fixed-point.
1784
1921
  - X/Y are in cell units: scale world coords down (e.g. Fbm2(pos.X / 800, pos.Y / 800, 4, 7)); features repeat at integer-cell scale. Use a different Seed per layer (trees vs rocks).
1785
- - SpawnTerrainFromHeights spawns a collidable terrain heightfield from code; heights are row-major (iy * SamplesX + ix), in cm relative to Position.Z. Terrain XY is centered on Position.
1922
+ - SpawnTerrainFromHeights spawns a collidable terrain heightfield from code (DATA exception lane - default ground is the #terrain recipe block, see <terrain_generation>); heights are row-major (iy * SamplesX + ix), in cm relative to Position.Z. Terrain XY is centered on Position.
1786
1923
  - terrain.GetTerrainHeightAt(X, Y) returns the world-space surface Z at world (X, Y) (bilinear); errors if the caller has no heightfield or (X, Y) is outside it.
1787
1924
  - Terrain grass: every terrain grows sparse built-in grass automatically; grass_visible = false / terrain.SetTerrainGrassVisible(false) removes it entirely (deserts, cities). The ground under the blades is the terrain's own splat/material — style it with `#terrain_splat` rules or terrain layers. Set from code via terrain.SetTerrainGrass / terrain.SetTerrainGrassWeightmap / terrain.SetTerrainGrassHeightCm / terrain.SetTerrainGrassStyle / terrain.SetTerrainGrassVisible (all per-terrain), or declaratively on terrain blocks (grass_model = "mesh.X", grass_material = "material.X", grass_weightmap = "image.X" or a #texture color, grass_height_cm = 28, grass_visible = true/false, grass_style = "blades"). No model = built-in grass: cheap alpha-cut tuft cards by default, or geometry blades via SetTerrainGrassStyle(1) (heavier, best with SetGrassSpacingCm 8..15 for dense hero fields). Custom #mesh blade budget: tiny tuft (<=100 verts), single material slot, bottom-anchored at Z=0; blade size comes from authoring the mesh at the right cm size, then grass_height_cm rescales it (28 = as-authored). Global tuning calls: SetGrassSpacingCm, SetGrassTerrainActiveDistanceM, SetGrassCullDistanceM, SetGrassFadeDistancePercent, SetGrassFarThinningPower; look calls: SetGrassRootColor, SetGrassTipColor, SetGrassBrightnessVariationPercent, SetGrassSizeVariationPercent, SetGrassHeightMinCm, SetGrassHeightMaxCm, SetGrassBladeWidthPercent, SetGrassWavePeriodCm, SetGrassWindStrengthCm, SetGrassWindSpeedPercent.
1788
1925
  - The same noise is available in #shader WGSL: ori_hash2(x, y, seed), ori_value_noise2(x, y, seed), ori_fbm2(x, y, octaves, seed) (f32; seed/octaves u32/i32), ori_hash2_cell(cx, cy, seed) -> u32. Matches the Weave functions exactly at integer lattice points, so sim placement and shader visuals can share one layout.
@@ -1813,18 +1950,105 @@ event OnSpawned {
1813
1950
  </procedural_generation>
1814
1951
 
1815
1952
  <terrain_generation>
1816
- - TerrainHeights* ops run in Rust (fast, deterministic): build large terrains with a few op calls instead of per-sample Weave loops. Heights list = raw cm Numbers, row-major; op coords in CELL units. world XY of cell (ix,iy) for a terrain centered at P: P.x + (ix - (SamplesX-1)/2) * CellSize (same for y).
1817
- - Workflow: heights.Resize(sx*sy, 0) -> TerrainHeightsNoise base -> shape ops -> SpawnTerrainFromHeights. Masks are just more Number lists (0..1): build with MaskFromSlope/MaskFromHeight or by running Flatten ops on a zeroed list, combine with TerrainHeightsBlend.
1818
- - Flat gameplay areas: TerrainHeightsFlattenDisc/Rect/Path AFTER noise/erosion so nothing roughens them again. ThermalErode + RemapCurve make raw noise look natural (erode first, then remap for plateaus/cliffs).
1819
- - Prefer 129..257 samples per axis (513 max; big lists inflate sim state generate in OnSpawned local vars so they are GC'd after spawn).
1820
- - GroundHeightAtXY(x, y) -> world Z cm of the topmost ground at world XY (downward raycast over terrain + static colliders; 0 if nothing). Use for placing props/villages on generated ground; works immediately after SpawnTerrainFromHeights in the same event.
1953
+ Default terrain - use for ALL ground (open worlds, islands, arenas, RTS maps): declare ONE
1954
+ `#terrain <Name> ... #end` block (top level, next to #texture/#adjuster) and
1955
+ `SpawnTerrainRecipe(Position)` collidable open-world ground, the same field
1956
+ everywhere (no grid, no size cap, no baking, no heights list in sim state; renders to 8 km
1957
+ by default). EVERY key is optional (a bare block is a complete, good-looking eroded
1958
+ terrain — the `eroded_mountain` preset); state only what you change — usually just the
1959
+ mound scale:
1960
+ #terrain ErosionField
1961
+ preset = eroded_mountain // optional (it is the default and currently the only preset)
1962
+ mound_height_m = 192 // peak height (world meters; resolution-independent)
1963
+ mound_size_m = 256 // base mound footprint (meters)
1964
+ water_m = 58 // water datum (extension band coloring)
1965
+ #end
1966
+ All keys (= eroded_mountain default): mound_size_m 300, mound_octaves 3, mound_gain 0.3
1967
+ (per-octave base amplitude factor; 0.1 near-mono smooth), mound_height_m 300, mound_seed 11,
1968
+ erode_from_m 2, erode_to_m 170 (erosion strength ramps 0..1 over this pre-erosion altitude
1969
+ band), erosion = ori (OriErosion, docs/ori_erosion_spec.md), gully_size_m 115 (first-octave
1970
+ gully feature size, meters), gully_octaves 3, gully_gain 0.4, gully_lacunarity 2 (erosion
1971
+ octave spectrum; Fewes reference 0.5/2/5oct), gully_depth_m 7.2 (erosion amount),
1972
+ slope_strength 1.5, branch_strength 2, gully_seed 21, water_m 0.
1973
+ // in OnSpawned:
1974
+ var terrain = SpawnTerrainRecipe(150, Origin())
1975
+ Gameplay flats in the block (repeatable lines, meters from the terrain center, author order,
1976
+ max 32 rows total; carve_path uses one row per segment): `flatten_disc = x, y, radius,
1977
+ height, blend`; `flatten_rect = x, y, half_w, half_h, height, blend`; `carve_path = width,
1978
+ depth, x0, y0, x1, y1, ...` - flats pin ground to an absolute height (base spawn/build
1979
+ pads), carves cut channels (paths, moats); they collide and render identically
1980
+ everywhere. Optional `smooth` (0..1, default 0, named form only) on carve_path/ridge_path
1981
+ curves the path through the SAME points (knobs stay on them): 0 = straight segments,
1982
+ 1 = fully curved.
1983
+ Point rows, stamps, and carve/ridge also parse named args after any positional
1984
+ prefix: `cone = position=(640, -180), radius=130, height=95`; paths take
1985
+ `carve_path = width=10, depth=6, smooth=1, points=[(45, 0), (300, -60)]` - arg names =
1986
+ the signature names with x, y as position=(x, y); named optionals omit in any order.
1987
+ Landform rows (same grammar/cap): `cone|dome|crater|volcano|mesa = x, y, radius,
1988
+ height[, seed]` - signed relief composed into the base BEFORE erosion, so gullies carve
1989
+ through them like native terrain (flats/carves apply after and win locally).
1990
+ Line landform: `ridge_path = width, height, x0, y0, x1, y1, ...` - a noise-varied crest
1991
+ along the polyline (mountain ranges), eroded like other landforms; `smooth` applies too.
1992
+ Layer rows (same grammar/cap): `noise = height_m, size_m, octaves, gain, seed[, mode]` -
1993
+ additive base-noise layer; stack several sizes for multi-scale relief (continents + hills +
1994
+ detail). mode = TerrainHeightsNoise's table (0 fbm, 1 ridged, 2 billow, 3 voronoi, 5 spike
1995
+ - inverted voronoi, stalagmite/karst fields; omitted = 4 gradient - signed, stacks without
1996
+ raising the ground mean; other modes add 0..height).
1997
+ `warp = strength_m, size_m, seed` (max one) - domain-warps the base + noise layers (never
1998
+ authored landform positions), killing grid-straight noise lines. Both feed erosion like
1999
+ native relief.
2000
+ Optional row name between kind and '=' (`volcano Vesuvius = 220, 0, 80, 120`, A-Za-z0-9_):
2001
+ shown at the row's editor control point and the shared handle ("move Vesuvius").
2002
+ `#terrain_modifier <Name> ... #end` (top level): a reusable named group of spatial rows
2003
+ (flats/carves/landforms/ridges - no warp). `area = radius[, blend]` bounds the template
2004
+ with a smoothstep edge (blend defaults to radius/4) and legalizes `noise` member rows,
2005
+ masked to the area - spike fields, karst patches. `param <name> = <default>` lines declare
2006
+ per-stamp knobs; member slots may use a param name in place of a number (`cone =
2007
+ position=(0, 0), radius=spread, height=peak`); member positions default to the template
2008
+ origin. Stamp instances inside #terrain: `modifier = Name, x, y[, scale[, seed_offset]]
2009
+ [, <param>=<value>...]` - members count toward the row cap; scale
2010
+ multiplies sizes and relief amounts (flatten pad heights stay absolute); the stamp is one
2011
+ control point moving the whole group.
2012
+ Runtime digging DURING play: TerrainDig / TerrainFlatten edit a sparse height overlay on
2013
+ the recipe terrain (collision + display + queries update; synced, join/rollback-safe;
2014
+ cannot tunnel under). Edits materialize sparse 32x32-cell overlay tiles (1024-tile budget,
2015
+ over = logged no-op) and re-bake the terrain display: per-action cost, not per-frame.
2016
+ Digs persist in an engine-written #terrain_overlay block (leave it verbatim). Nav after
2017
+ digs: reset the grid, then re-run TileGridSetWalkableFromTerrain (it only closes tiles);
2018
+ react to event OnTerrainEdited (once per edited frame) or poll GetTerrainEditVersion().
2019
+ TerrainOverlayClear() removes every edit. Terrain lagging or flickering during edits =
2020
+ re-deriving terrain per frame: call edit ops (TerrainOverlayClear included) only when an
2021
+ input changed, never every frame in Update.
2022
+ Block terrain (Timberborn / Minecraft-class): `blocks = true` in the block makes the ground
2023
+ blocks of the terrain tile size (SpawnTerrainRecipe's optional TileSizeCm arg; default 100) - collision, raycasts and queries per
2024
+ block (optional `block_strata = 1 1, 2 3, 3` = surface number + depth m per band, last band
2025
+ bottomless; numbers index the terrain's #terrain_surfaces, 1-based). Columns:
2026
+ VoxelSetHeight(Position, Height) sets the column under Position to Height cm,
2027
+ VoxelSetHeightBox(Min, Max, Height) every column in the XY rect; TerrainDig/TerrainFlatten
2028
+ also work (block-snapped). `overhangs = true` adds tunnels, caves and floating blocks:
2029
+ VoxelSet / VoxelFillBox / VoxelFillSphere edit blocks (Id 0 = air, N = N-th surface), VoxelGet
2030
+ reads one; height tools overwrite only the span they move, so caves below stay. Without
2031
+ overhangs the block calls are errors. RaycastPosition hits block faces: hit +
2032
+ Direction.GetNormal() is inside the block, hit - Direction.GetNormal() is the cell to place on.
2033
+ Same session-overlay rules (TerrainOverlayClear resets).
2034
+ - GroundHeightAtXY(x, y) -> world Z cm of the topmost ground at world XY (downward raycast over terrain + static colliders; 0 if nothing). Use for placing props/villages on generated ground; works immediately after spawning the terrain in the same event.
1821
2035
  - TerrainSlopeAtXY(x, y) -> slope01 of the topmost terrain at world XY (0.5 = 45deg; no terrain = 1, treat void as unwalkable). Slopes steeper than ~0.5 silently immobilize walkers - keep unit routes below it or block those tiles/edges in the nav grid (see <tile_pathfinding>).
2036
+ - Water: sea/lakes at one level -> SetWaterEnabled(true, seaLevelCm); pools/streams/falls at other heights -> flat SetIsWater(true) objects (SetWaterFlow steers rivers/falls).
2037
+
2038
+ DATA exception lane - TerrainHeights* ops on a heights List -> SpawnTerrainFromHeights
2039
+ (bounded baked grid). Use ONLY when the look needs grid-global ops (routed drainage,
2040
+ iterative erosion, imported heights, tier maps) or the world already runs on a DATA
2041
+ grid. Lane switches change the product - tell the user in one line: DATA trades away
2042
+ infinite extent, terrain sliders/knobs and runtime digging for grid-global ops on a
2043
+ bounded quantized grid.
2044
+ - TerrainHeights* ops run in Rust (fast, deterministic): a few op calls, never per-sample Weave loops. Heights list = raw cm Numbers, row-major; op coords in CELL units. world XY of cell (ix,iy) for a terrain centered at P: P.x + (ix - (SamplesX-1)/2) * CellSize (same for y). Workflow: heights.Resize(sx*sy, 0) -> TerrainHeightsNoise base -> shape ops -> SpawnTerrainFromHeights. Masks are just more Number lists (0..1): build with MaskFromSlope/MaskFromHeight or by running Flatten ops on a zeroed list, combine with TerrainHeightsBlend.
2045
+ - Flat gameplay areas: TerrainHeightsFlattenDisc/Rect/Path AFTER noise/erosion so nothing roughens them again. ThermalErode + RemapCurve make raw noise look natural (erode first, then remap for plateaus/cliffs).
2046
+ - Prefer 129..257 samples per axis (513 max; big lists inflate sim state — generate in OnSpawned local vars so they are GC'd after spawn). Keep CellSize under ~500 cm: stretching cells to span a bigger map makes the ground faceted and collision coarse — km-scale ground is the #terrain recipe's job (no size cap).
1822
2047
  - TerrainHeightsFromTiers(Heights, SamplesX, SamplesY, Tiers, TierCm) REPLACES the field with plateau heights from an ASCII tier map in a triple-quoted Text (digits 0-9 = tier, h = tier * TierCm; '.' = 0; 'r' = ramp graded toward its lower neighbor; rows = lines, FIRST row = MAX Y / north). Hand-drawn RTS cliff maps: one char per nav tile, then TileGridSetEdgesFromTerrain(MaxStepCm = TierCm/3) derives the cliff edges; compose Smooth/Erode/splat after.
1823
2048
  - Realism combo: Warp after base noise (kills grid-straight lines), then ErosionFilter (fast stateless gullies/ridges; run AFTER RemapCurve so the detail lands at final scale, and its look continues sub-cell on the GPU display surface). Avoid HydraulicErode/ThermalErode by default — slow iterative CPU sims; reach for HydraulicErode only when routed drainage or its flow/deposit masks are truly needed. MaskFromFlow gives a wetness mask WITHOUT eroding.
1824
2049
  - Terrain-following rivers: FlowPath traces downhill from a spring cell into a CarvePath-ready point list (stops at sea level = MinHeightCm). Check PathXYOut has >= 4 numbers (2 points) before carving.
1825
2050
  - Roads that climb: GradePath lerps the flatten height start->end along the path (use terrain heights at the endpoints); FlattenPath only does constant height.
1826
2051
  - Prop placement: build a 0..1 mask (MaskFromSlope inverted for flat ground, x height band etc.), TerrainScatterOnMask -> flat x,y cell list, then per point: world XY via the cell formula, Z via GroundHeightAtXY after spawning the terrain. For woods, prefer one `#forest` region over the terrain instead of spawn loops (see <forest_dsl_usage>).
1827
- - Water: sea/lakes at one level -> SetWaterEnabled(true, seaLevelCm); pools/streams/falls at other heights -> flat SetIsWater(true) objects (SetWaterFlow steers rivers/falls).
1828
2052
  - Authoring landforms: RidgePath = draw a mountain range as a polyline. Stamp = drop cones/domes/craters/volcanoes/mesas. StampHeights = bake any small heightfield once, re-stamp it at many spots/scales.
1829
2053
  - MaskFromCurvature picks ridges/rims (or valley floors with End<Start) for splat/scatter decisions.
1830
2054
  - terrain.SetTerrainSplatMask(mask, sx, sy) feeds any 0..1 mask list into the `user` input plane of #terrain_splat rules (e.g. flow mask -> wet dirt): visual layering driven by generated data.
@@ -1847,34 +2071,13 @@ TerrainHeightsCarvePath(heights, sx, sx, river, 4, 700, 6)
1847
2071
  var terrain = SpawnTerrainFromHeights(heights, sx, sx, 150, Origin())
1848
2072
  terrain.SetTerrainLayers(Material.RockTex, Material.SnowTex, Material.SandTex)
1849
2073
 
1850
- Seamless infinite-looking worlds: declare ONE `#terrain <Name> ... #end` recipe block (top level,
1851
- next to #texture/#adjuster) and build from it — the display extension then continues the exact
1852
- same field beyond the grid. EVERY key is optional (a bare block is a complete, good-looking
1853
- eroded terrain — the `eroded_mountain` preset); state only what you change — usually just
1854
- the mound scale:
1855
- #terrain ErosionField
1856
- preset = eroded_mountain // optional (it is the default and currently the only preset)
1857
- mound_height_m = 192 // peak height (world meters; resolution-independent)
1858
- mound_size_m = 256 // base mound footprint (meters)
1859
- water_m = 58 // water datum (extension band coloring)
1860
- #end
1861
- All keys (= eroded_mountain default): mound_size_m 300, mound_octaves 3, mound_gain 0.3
1862
- (per-octave base amplitude factor; 0.1 near-mono smooth), mound_height_m 300, mound_seed 11,
1863
- erode_from_m 2, erode_to_m 170 (erosion strength ramps 0..1 over this pre-erosion altitude
1864
- band), erosion = ori (OriErosion, docs/ori_erosion_spec.md), gully_size_m 115 (first-octave
1865
- gully feature size, meters), gully_octaves 3, gully_gain 0.4, gully_lacunarity 2 (erosion
1866
- octave spectrum; Fewes reference 0.5/2/5oct), gully_depth_m 7.2 (erosion amount),
1867
- slope_strength 1.5, branch_strength 2, gully_seed 21, water_m 0.
1868
- // in OnSpawned:
1869
- TerrainHeightsFromTerrain(heights, ridges, sx, sx, 150) // grid heights + ridge map from the block
1870
- SetTerrainDisplayExtension(5000) // same generator continues to 5km, render-only
1871
-
1872
2074
  Two terrain display types:
1873
- - DATA terrains (heights from ops/edits/#terrain_bytes): bounded; paint with SetTerrainLayers
2075
+ - DATA terrains (heights from ops/edits/#terrain_bytes): bounded baked grids — use for
2076
+ hand-shaped heightfields, element-wise edits, or uploaded terrain; paint with SetTerrainLayers
1874
2077
  and/or baked #terrain_splat rules (weights or out.albedo colormap). Baked paint covers the
1875
2078
  grid only - fine, nothing extends anyway.
1876
- - RECIPE terrains (#terrain block + TerrainHeightsFromTerrain, verified at load): the display
1877
- renders the generator function everywhere, so the grid border disappears. Baked #terrain_splat
2079
+ - RECIPE terrains (#terrain block + SpawnTerrainRecipe): unbounded, collidable everywhere; the
2080
+ display renders the generator function, so there is no border. Baked #terrain_splat
1878
2081
  paint CANNOT follow (unique texels don't extend); paint these with a LIVE terrain #shader
1879
2082
  material instead (SetTerrainMaterial with a #shader material): the @fragment body gets `ctx`
1880
2083
  (world_pos, normal, uv - continues past [0,1] in the extension - and ctx.user = the
@@ -1886,12 +2089,13 @@ Two terrain display types:
1886
2089
  <procedural_generation_batches>
1887
2090
  - InstanceBatch: thousands of decorative copies of one model as ONE display object (pebbles, rubble, grass tufts). Instances have no physics/logic/picking and are invisible to the editor - never clickable, movable, or saved there; the layout lives in this code (edit by index via SetInstance/RemoveInstance, or ClearInstances + re-add). Identical static meshes already render as one instanced draw either way - a batch saves per-object sim/load cost (spawn, rollback state, join checkpoints), not draw calls. Copies a human should hand-place or that need any interactivity: SpawnObject. Drifting petals/leaves/debris: #particles mesh components, not a batch. Animated look on scatter (sway/shimmer): vertex #shader on the model's material - never per-frame batch edits (SetBatchOrigin included), and batch handles reject Object.* calls like SetRenderOffset.
1888
2091
  - Batches outlive their creator; only Destroy() frees a slot (128 live max). One batch per model kind, edited in place - don't create per region or per round.
2092
+ - Architecture repeats the same way - window frames, balusters, fence posts, shutters - but when instances follow a regular grid or must stay world-text-authored, prefer a `#placement` clause (see <placement_usage>) over a batch.
1889
2093
  - Model can be a basic primitive (Ori.model.cube/cone/wedge/...); add SetBatchMaterial(Material.X) for textured scatter without any market asset.
1890
2094
  Example (2000 pebbles):
1891
2095
  var pebbles = CreateStaticInstanceBatch(Ori.alias.model.pebble, Origin())
1892
2096
  for i in 0..2000 {
1893
2097
  var s = 6 + Hash2(i, 3, 5) * 14
1894
- pebbles.AddInstance(Vector((Hash2(i, 0, 5) - 0.5) * 4000, (Hash2(i, 1, 5) - 0.5) * 4000, 0),
2098
+ pebbles.AddInstance(Vector((Hash2(i, 0, 5) - 0.5) * 4000, (Hash2(i, 1, 5) - 0.5) * 4000, s * 0.35), // z = half height: Offset is the CENTER
1895
2099
  Vector(0, 0, Hash2(i, 2, 5) * 360), Vector(s, s, s * 0.7), Vector(0.62, 0.6, 0.55))
1896
2100
  }
1897
2101
  </procedural_generation_batches>
@@ -1907,13 +2111,13 @@ for i in 0..2000 {
1907
2111
  - Clicking object requires CanCollide. DisplayObject and RegionObject are not clickable.
1908
2112
  - Only one instance of each event_type+event_options is allowed per class. Duplicates are a compile error.
1909
2113
  - Spawn timing: SpawnObject/SpawnAtTile/etc. queue OnPreSpawn/OnSpawned for next tick. The returned object is valid immediately (SpawnObject doesn't fail / doesn't return None), but its spawn events haven't run yet.
1910
- - OnSpawned ordering: Execution across different objects is unspecified and may be deferred. Do not rely on relative ordering for inter-object initialization; if dependencies exist, do the setup in an explicit build step or a later tick after all spawns are issued.
1911
- - OnPreSpawn runs once per instance, before any OnSpawned.
1912
- - All OnPreSpawn finish for the frame before the first OnSpawned runs
1913
- - Ordering between objects' OnPreSpawn is unspecified
2114
+ - OnPreSpawn runs once per instance, all before the first OnSpawned; spawn order across objects is unspecified and may defer - do inter-object setup in an explicit build step or a later tick.
1914
2115
  - GameCommand fires on every class that declares it when a user sends a command text message (headless build orders or chat-driven commands). Event.Text is the raw command line, Event.Sender the sender's player object. Parse Text yourself (e.g. "build house 3 4").
1915
2116
  - OnWorldReady fires once per load (again after RestartGame) when every scene object's OnSpawned has STARTED (run to its first yield), spilled load batches included. Poll IsWorldReady() for spawn/placement code that depends on procedurally built ground or rooms (terrain, BuildRoom) instead of Wait() timers; a handler that Wait()s mid-body may still be mid-flight at ready.
1916
2117
  - OnPlayerLeft fires on the leaver's player object the moment IsConnected() flips false (the body stays ~90s for reconnect; OnDestroy only at grace expiry): clear SetHoverText labels / roster rows here. OnPlayerReconnected fires when the SAME body resumes within the grace; a post-grace rejoin is a fresh spawn (OnSpawned).
2118
+ - LeftClickThisWorld/RightClickThisWorld fire only when no UI consumed the click; Event.ClickObject (None on miss)/ClickPosition/ClickGroundPosition are the click-frame hit - point-and-click worlds use these, not raw mouse events plus IsMouseOnUi guards.
2119
+ - Event.MousePosition/MouseDownPosition are in the 1600x900 base screen space (same space as UI rects and WorldToScreenPosition); Event.MouseViewportSize is the real viewport for letterbox math.
2120
+ - Broadcast events without ByThisPlayer (LeftClickThisWorld, LeftMouseClick, ...) run once per instance of every class declaring them - with N players a player-class handler runs N times per click; guard non-idempotent handlers with `if Event.Presser != Self { return }` or use the ByThisPlayer variant.
1917
2121
  </events>
1918
2122
  <event_list>
1919
2123
  event OnPreSpawn
@@ -2056,6 +2260,24 @@ event InventoryDropOnWorld
2056
2260
  event OnWorldReady
2057
2261
  event OnPlayerLeft
2058
2262
  event OnPlayerReconnected
2263
+ event LeftClickThisWorld
2264
+ Event.Presser .. Object
2265
+ Event.ClickObject .. Object
2266
+ Event.ClickPosition .. Position
2267
+ Event.ClickGroundPosition .. Position
2268
+ Event.MousePosition .. Position
2269
+ event RightClickThisWorld
2270
+ Event.Presser .. Object
2271
+ Event.ClickObject .. Object
2272
+ Event.ClickPosition .. Position
2273
+ Event.ClickGroundPosition .. Position
2274
+ Event.MousePosition .. Position
2275
+ event OnTerrainEdited
2276
+ event ActionInvokedByThisPlayer[Primary]
2277
+ Event.ActionText .. Text
2278
+ Event.ActionNumber .. Number
2279
+ Event.ActionNumber2 .. Number
2280
+ Event.ActionPosition .. Position
2059
2281
 
2060
2282
  </event_list>
2061
2283
 
@@ -2095,14 +2317,14 @@ event OnPlayerReconnected
2095
2317
  </frame_and_scheduling_model>
2096
2318
 
2097
2319
  <ui>
2098
- - Use ClickButton event to detect any button click. Event properties: Event.ButtonClicker, Event.ClickedButton
2320
+ - Use ClickButton event to detect any button click. Event properties: Event.ButtonClicker, Event.ClickedButton. Broadcasts to every class declaring the handler (a display-object manager class hears every button, count-expanded copies included).
2099
2321
  - event ClickThisUI fires when any UI element of this class (frame/image/label/button) is clicked; buttons additionally fire ClickThisButton/ClickButton. Both carry Event.ClickFraction: the 0..1 click position inside the element (x right, y down) — minimaps, sliders, pickers
2100
- - Drag-drop: mark elements draggable=true (or SetUiDraggable(true)). Engine handles press+move threshold, drag ghost and click suppression. UiDragStart fires on drag start (Event.Dragger, Event.DragElement); UiDrop on release (Event.Dragger, Event.DragElement, Event.DropElement = UI element under cursor or None, Event.DropObject = world object hit when dropped outside UI, Event.DropPosition). Both broadcast to every class defining the handler (player class or the element's class both work); ClickThisUI instead fires only on the clicked element's own class. A short press without movement still clicks normally.
2322
+ - Drag-drop: draggable=true (or SetUiDraggable(true)); the engine handles press+move threshold, drag ghost and click suppression (a short press still clicks). UiDragStart (Event.Dragger, Event.DragElement); UiDrop on release (+ Event.DropElement = UI under cursor or None, Event.DropObject = world object hit outside UI, Event.DropPosition). Both broadcast to every class defining the handler; ClickThisUI fires only on the element's own class.
2101
2323
  - Tooltip/cursor-follow UI lagging behind the mouse: never chase the cursor with per-frame offset loops - call SetUiFollowsCursor(true, dx, dy) once (owned screen canvas); the engine glues it to the real cursor. Scripts only refresh its text.
2102
- - SetCursorImage(ImageAsset) turns the OS hardware cursor into that image (zero-lag, unlike moving a UI element each frame); SetCursorImage(NONE) restores the stock cursor. Overload SetCursorImage(ImageAsset, HotspotX, HotspotY, Opacity): hotspot is 0..1 of the image ((0,0)=top-left default, (0.5,0.5)=image centered on the pointer - ideal drag ghost), opacity 0..1 scales the image alpha. Don't animate opacity per frame (each step builds a new OS cursor). The image is downscaled to <=64px; touch devices have no cursor, so mobile drag ghosts still need a UI element. Pattern: SetCursorImage(ghost, 0.5, 0.5, 0.8) in UiDragStart, SetCursorImage(NONE) in UiDrop.
2324
+ - SetCursorImage(ImageAsset) turns the OS hardware cursor into that image, zero-lag (NONE restores). Overload (ImageAsset, HotspotX, HotspotY, Opacity): hotspot 0..1 of the image ((0.5,0.5) = centered on the pointer - ideal drag ghost), opacity 0..1. Don't animate opacity per frame (each step builds a new OS cursor); image downscaled to <=64px; touch has no cursor - mobile drag ghosts stay UI elements. Pattern: SetCursorImage(ghost, 0.5, 0.5, 0.8) in UiDragStart, NONE in UiDrop.
2103
2325
  - Hit-testing ignores plain images/labels (clicks pass through), BUT any element whose class has script or with draggable=true becomes clickable and blocks hit tests under it. Keep overlay visuals (tooltips, custom drag ghosts) class-free and move them from another class's code, or they will swallow the hover/click you are trying to detect.
2104
2326
  - Inventories/hotbars/chests: see <inventory> — item clauses + BindInventory give a fully engine-synced slot UI.
2105
- - Minimap: an image element with image = "ori.image.engine.minimap" shows an auto-refreshing top-down snapshot of the largest terrain, spanning its XY bounds (terrain only — no objects/fog). Blips/camera rect = child frames placed via GetTerrainBoundsMin/Max; map clicks back to world XY with Event.ClickFraction.
2327
+ - Minimap: image = "ori.image.engine.minimap" = auto-refreshing top-down snapshot of the largest terrain across its XY bounds (terrain only). Blips = child frames placed via GetTerrainBoundsMin/Max; clicks map back to world XY with Event.ClickFraction.
2106
2328
  - ui object tags for world description: screenCanvas/worldCanvas/frame/textLabel/button; elements nest
2107
2329
  - Element handles in script: scene-declared elements resolve as Scene.X; children of a SpawnCanvas canvas via canvas.FindChildUI("Name")
2108
2330
  - Spawning saved UI at runtime: canvas-root saved block -> SpawnCanvas(SavedObject.X, pos); element-root block (frame/textLabel/button...) -> SpawnChildUI(SavedObject.X, ParentUI). SpawnObject rejects both.
@@ -2124,19 +2346,23 @@ event OnPlayerReconnected
2124
2346
  - Word values: child_align = left|center|right (x) or top|center|bottom (y); align_self_x/y = auto|start|center|end|stretch (per-child override of child_align; stretch fills the cross axis).
2125
2347
  - frame = plain/absolute container (no auto-layout). Use it for screen-anchored HUD corners and for elements you reposition at runtime via SetUiOffset. A frame/grid without `size` inherits the parent's size. Position a child by ONE of: offset; anchor; insets.
2126
2348
  - anchor = (x, y): pin the element to a spot on the parent (x=left|center|right, y=top|center|bottom). It also sets the pivot to match, so anchor=(center,center) centers, anchor=(center,bottom) makes a bottom bar, anchor=(right,bottom) a corner. `offset` still nudges, always in raw screen units (+x right, +y down) regardless of anchor - from a bottom anchor, inward is negative y. Decouple with pivot=(x,y) (or anchor_x/y + pivot_x/y) when the element's own pivot must differ from the parent attach point.
2127
- - inset = N | (x, y): pin all four edges to the parent content rect so the child fills parent - inset (per-side: inset_left/inset_right/inset_top/inset_bottom). Use it to inset content inside a button/frame (e.g. an icon: inset=10). insets differ from margin (a child's outer spacing inside a stack/grid) and padding (a container's inner inset applied to ALL its children); a button is not a container, so inset the child rather than padding the button.
2128
- - cover = screen (frame): rect = the player's FULL real window instead of the 1600x900 base box - backdrops/vignettes reach wide-monitor edges (unflagged UI lays out in the centered base box, leaving side bands there). Children anchor inside it normally, so a HUD wrapped in a cover frame hugs true screen corners. Owned screen canvases only.
2349
+ - inset = N | (x, y): pin all four edges so the child fills parent - inset (per-side: inset_left/right/top/bottom; icon in a button: inset=10). Differs from margin (child's outer spacing in a stack/grid) and padding (container's inner inset for ALL children); a button is not a container - inset the child, don't pad the button.
2350
+ - cover = screen (frame): rect = the player's FULL real window instead of the centered 1600x900 base box - backdrops/vignettes reach wide-monitor edges. Children anchor inside normally (a cover-wrapped HUD hugs true corners). Owned screen canvases only.
2129
2351
  - visible = false starts any element hidden (there is no `hidden` field); SetVisible(true) reveals it - same flag.
2130
2352
  - clip_to_bounds = true clips children (and hit-testing) to this rect; block_input = true blocks clicks passing through. Containers (frame/vStack/hStack/grid) default block_input = TRUE - set block_input = false on overlay panels that must not swallow clicks on the world behind them (other elements default false).
2131
2353
  - Interactable elements (buttons, classed elements, draggables) consume clicks before the world: no click-through move/shoot/ClickThisObject under them, so no guard code needed. block_input additionally swallows clicks on non-interactable containers; a decorative classed element that must not eat clicks (world clicks, or a button it is layered over) sets click_through = true.
2132
- - Scrollable container: set clip_to_bounds=true + block_input=true on the container, then SetUiScrollOffset(X, Y) shifts its content (children move by -offset; rect/clip stay). Read back with GetUiScrollOffsetX/Y(). Wire event OnMouseScroll { } (Event.ScrollDelta, positive = wheel up) to adjust Y. Wheel over input-blocking UI does not zoom the camera. Authored start offset: scroll_offset = (x, y).
2354
+ - Scrollable container: clip_to_bounds=true + block_input=true, then SetUiScrollOffset(X, Y) shifts children by -offset (rect/clip stay; readback GetUiScrollOffsetX/Y). Wire OnMouseScroll (Event.ScrollDelta, positive = up) to adjust Y; wheel over blocking UI does not zoom. Authored start: scroll_offset = (x, y).
2133
2355
  - Rounded corners + border work on frame/button/progressBar (textLabel: neither; image: corner_radius only): corner_radius = N (base units, all 4 corners), border_width = N (base units; border shows only when > 0), border_color = #RRGGBB | #RRGGBBAA | (r,g,b). Authoring-only: no SetUi* simcall changes these at runtime.
2134
2356
  - progressBar: color = fill, background_color = track; fill ratio via SetProgress(Value, MaxValue).
2135
- - count = N (2..=256, canvas child elements): parse-time repeat the element and its whole subtree expand into N copies named <Name>0..<Name>N-1 (e.g. `frame Slot { count = 9 ... }` -> Slot0..Slot8, each resolvable by name). Use for marker/gauge/slot pools instead of hand-numbering; copies stack until code positions them via SetUiOffset; resolve copies dynamically via Panel.FindChildUI("Slot" + i). Counts nest (rows x cells).
2357
+ - Animate UI: SetUiOffset/SetUiSize/SetUiOpacity(..., Secs[, Ease]) and SetUiColor(Color, Secs) glide to the target (Ease 0-5 = linear/in/out/in-out(default)/out-back/out-bounce; color always in-out); a same-property call replaces the glide, the instant call cancels it. Pop idiom: instant call, then the glide overload in the same frame - the glide starts FROM the instant value (snap-then-ease, no timer state). MoveUiToFront() = draw on top of siblings. Never hand-roll per-frame Lerp loops for UI.
2358
+ - hover_color/hover_offset = (x, y)/hover_scale/hover_secs: viewer-local hover styling, applied instantly by the viewing client - the element (or nearest styled ancestor) shifts, scales around its center, tints toward hover_color and draws on top; sim and hit tests unchanged, touch inert. Gameplay logic still polls IsMouseOnUi.
2359
+ - rotation = deg (any element; SetUiRotation(Deg[, Secs[, Ease]]), GetUiRotation): spins the element AND its subtree around its center, +CW - card fans, dials, compasses. Hit-testing follows; clipping stays the screen-AABB scissor.
2360
+ - count = N (2..=256, canvas child elements): parse-time repeat - the element + its subtree expand into N copies <Name>0..<Name>N-1 (frame Slot { count = 9 } -> Slot0..Slot8). Use for marker/gauge/slot pools; copies stack until positioned via SetUiOffset; resolve via Panel.FindChildUI("Slot" + i). Counts nest. An attached class (frame Card : CardSlot { count = 10 }) instantiates per copy - each copy gets its own class instance.
2136
2361
  - image nine_slice = (uv_frac, border): scalable panel art — the outer uv_frac (0..0.5) of the texture becomes a fixed border-unit rim (corners keep size, edges/center stretch). Use for stretchy frames/panels from one texture.
2137
- - frame/vStack/hStack/grid also accept image = "image.*" as a background fill (drawn over color/border, under children; nine_slice applies). Combine with material= freely.
2362
+ - image sheet = (cols, rows) + optional fps = N (default 8): the image is a sprite-sheet frame grid, auto-playing row-major at fps (display-side). SetUiSheetRange(A, B) loops frames A..B inclusive (idle/walk stances); SetUiSheetOnce(A, B) plays A..B once then returns to the standing loop/hold (attacks, hits — no Wait bookkeeping); SetUiSheetFrame(I) holds frame I. Frames 0-based; excludes nine_slice + corner_radius. flip_x = true mirrors the drawn image horizontally (face left without regenerating art).
2363
+ - frame/vStack/hStack/grid also accept image = "image.*" as a background fill (drawn over color/border, under children; nine_slice applies). material = "M" runs a custom #shader on the element - animated glow/shine/dissolve (contract: shader_dsl skill, UI section).
2138
2364
  - text/button shadows are off by default; set shadow_color = #000000B4 (or any alpha > 0) to enable; 6-digit hex is fully opaque (#000000 = solid shadow), 8 digits control alpha.
2139
- - UI text covers Latin-1 plus common symbol blocks (punctuation – — … •, arrows, geometric shapes ◆●▶, misc symbols ★♦⚙, dingbats ✦✔). Emoji and CJK render as NOTHING (validation rejects). Icons still look best as image assets.
2365
+ - UI text covers Latin-1 plus common symbol blocks (punctuation – — … •, arrows, geometric shapes ◆●▶, misc symbols ★♦⚙, dingbats ✦✔) in every font; other scripts (Thai, CJK, Cyrillic) need a font that covers them (font=/SetFont/SetDefaultFont). Characters a font lacks draw as a box and fail validation; emoji never render; Thai marks stack approximately (unshaped). Icons still look best as image assets.
2140
2366
  - text_color defaults white; a button with a background color set but no text_color auto-picks a readable default (white on dark, near-black on light).
2141
2367
  - Colors accept optional 8-digit hex alpha (e.g. #3A3A3AFF) in addition to #RRGGBB and (r,g,b).
2142
2368
  - Two-stop gradients: gradient(colorA, colorB[, angle_deg]) (linear; angle 0 = top->bottom, 90 = left->right) or radial(inner, outer) (center->corners). ONLY on frame/vStack/hStack/grid `color` (fill) and on `border_color` of frame-like UI — NOT on button/textLabel colors (wrap the button in a gradient frame instead).
@@ -2201,18 +2427,19 @@ event InventoryDropOnWorld {
2201
2427
  - use the function PlayAnimationOnRepeat to loop animations like run forever
2202
2428
  - PlayAnimationOnRepeat(Ori.anim.default.goblin.run)
2203
2429
  - PlayAnimation(Ori.anim.default.goblin.attack)
2204
- - a marketplace model's own clips: Ori.anim.<creator>.<model>.<clip> (e.g. Ori.anim.vk7t7mestuxl_5378.murlocfishgi.attack); clip names are exact-case
2430
+ - a marketplace model's own clips: Ori.anim.<creator>.<model>.<clip> (e.g. Ori.anim.vk7t7mestuxl_5378.murlocfishgi.attack); clip names are exact-case; a world `#clip Name on model.<creator>.<model>` block (canonical bone names) adds or shadows one for this world
2205
2431
  - Timing a clip to a moment (hand/bat/racket meeting a ball): call PlayAnimationHittingContactIn(Anim, ContactPercent, SecondsUntilContact) every frame - it returns false and does nothing while still too early, then fires rate-scaled so ContactPercent lands exactly on contact, and refuses to restart a clip it already started (no 'started' guard var needed). GetAnimationLeadSeconds tells how early it can fire. Never derive a trigger delay from GetAnimationDuration by hand.
2206
2432
 
2207
2433
  - Rigged store characters can also play canonical clips (world `#clip Name on canon` blocks -> Ori.anim.canon.Name) and other humanoid rigs' clips (e.g. a template mesh's Ori.anim.mesh.<Mesh>.walk) — the engine retargets by bone-name map, display-only
2434
+ - `#clip Name on model.<creator>.<name>` registers Ori.anim.<creator>.<name>.Name for that store model; canon/model clip bodies are authored on the canonical template rig's bones — clips for a richer custom rig belong on its rigged #mesh
2208
2435
 
2209
2436
  - Autoplay Animations are turned on by default for mesh-backed objects (players, crowds, physics/world objects) whose mesh has a default `.idle` animation asset (anim.<mesh>.idle)
2210
2437
  - the clip is picked automatically: .idle when still, .run when moving (falling back to .walk), .jumpHover when airborne; camera-locked players also use .runBack/.runLeft/.runRight by movement direction
2211
2438
  - using PlayAnimation will stop these animations until the animation is finished
2212
- - DisableAutoMoveAnimations() to turn this off
2439
+ - DisableAutoMoveAnimations() to turn this off - durable (survives SetModel and late-downloading market clips); a script-posed prop whose model ships humanoid clips (market props can) also needs one StopAnimation() to drop an already-applied clip
2213
2440
  - Rigged humanoid models (store characters or DSL rigs) that ship no `.idle` clip idle/walk/run (jumpHover airborne) with the built-in canonical clips automatically (display-only retarget); models with their own clips always use them
2214
2441
  - Custom/procedural character: author ONE rigged #mesh (bone(...) joints + optional idle/walk #clips, standard bone names — see the humanoid example in the mesh skill) and SetModel it; auto move-animations then drive it. AttachTo assembly is for props/machines (turret, arm -> gun), not walking bodies
2215
- - Procedural bone layer on any bone(...) rig (display-only, max 16 bones): SetBoneRotation(Mix) replaces/blends a joint over the playing clip, BoneLookAt aims one at a world point, PlayUpperBodyAnimation layers a clip over the base walk. Head-track + breathe over auto-walk:
2442
+ - Procedural bone layer on any bone(...) rig (display-only, max 16 bones): SetBoneRotation(Mix) replaces/blends a joint over the playing clip, BoneLookAt aims one at a world point, PlayUpperBodyAnimation layers a clip over the base walk. Rotations act in each bone's LOCAL REST frame: imported-rig joint axes rarely match world axes (probe one bone with a small test rotation), and mirrored L/R bones usually need SAME-signed values for opposite swings. Head-track + breathe over auto-walk:
2216
2443
  forever {
2217
2444
  BoneLookAt("Head", Friend.GetCenterPosition(), 55, 240)
2218
2445
  SetBoneRotationMix("Torso", Vector(0, 2, 0), 1, 0.7, 1) // additive lean; alternate targets on a timer to breathe
@@ -2225,7 +2452,7 @@ event InventoryDropOnWorld {
2225
2452
 
2226
2453
  <world_description_examples>
2227
2454
  These examples are trimmed-down teaching games: copy their patterns, not their scope — a real request usually deserves more content and polish than any example here.
2228
- Examples by genre: stardew_valley (farming/life-sim), vampire_survivor (top-down action/swarm), breakout (2D arcade/side-view), dota (top-down hero/lane).
2455
+ Examples by genre: stardew_valley (farming/life-sim), vampire_survivor (top-down action/swarm), breakout (2D arcade/side-view), dota (top-down hero/lane), rts_skirmish (RTS: cliff terrain, nav grid, crowd squads).
2229
2456
  <example_breakout>
2230
2457
  // Breakout (Fixed-Bounds Top-Down)
2231
2458
  // Multiplayer: shared board.
@@ -2996,10 +3223,12 @@ screenCanvas MainUI : UIManager {
2996
3223
  // hold poses the first-person arms (hands + a sliver of forearm), and
2997
3224
  // EnableLocalWeaponView(Offset, RotationDeg, Scale, FovDegrees) renders weapon+arms
2998
3225
  // through the dedicated viewmodel pass (own FOV, never clips into walls).
2999
- // No SetWeaponConfig, no anchors, no clips required.
3226
+ // No SetWeaponConfig, no anchors, no clips required (library weapons).
3000
3227
 
3001
3228
  #class player
3002
3229
  var T: Number
3230
+ var Bob: Number
3231
+ var Kick: Number
3003
3232
 
3004
3233
  event OnSpawned {
3005
3234
  SetCameraToFirstPerson()
@@ -3009,8 +3238,14 @@ event OnSpawned {
3009
3238
  }
3010
3239
 
3011
3240
  event Update {
3012
- // Walk into the wall ahead: arms stay held, the viewmodel never clips.
3241
+ // Sway/bob/kick = re-call EnableLocalWeaponView every frame: it is a cheap
3242
+ // whole-config overwrite, so the driven offsets ARE the viewmodel animation.
3013
3243
  T = T + 1
3244
+ Bob = (T * 6).SinDegree() * 1.2 // ~1 Hz walk bob, cm
3245
+ Kick = Kick * 0.85 // recoil recovery; tap Space to fire a kick
3246
+ if IsKeyPressedByThisPlayer(Key.Space) { Kick = 6 }
3247
+ EnableLocalWeaponView(Position(15, -22 + Kick * 0.5, 38 + Bob - Kick), Vector(2 - Kick, 4, 0), 1.0, 55)
3248
+ // Walk into the wall ahead: arms stay held, the viewmodel never clips.
3014
3249
  if T > 120 {
3015
3250
  if T < 420 {
3016
3251
  MoveInDirectionContinuously(Vector(0, 1, 0), 620)
@@ -3039,7 +3274,7 @@ object Wall {
3039
3274
  <example_grass_meadow_adjuster_demo>
3040
3275
  // Grass meadow: built-in tuft-card grass on gentle hills, all global grass/ground
3041
3276
  // look knobs declared (values apply on load; sliders live in the Terrain inspector -
3042
- // click the terrain; Ctrl+S folds edits back into this block). Ground under grass auto-tints.
3277
+ // click the terrain; Ctrl+S folds edits back into this block).
3043
3278
 
3044
3279
  #adjuster
3045
3280
  section "Grass"
@@ -3197,12 +3432,16 @@ display WorldGenObj : WorldGen { visible = false }
3197
3432
  // RTS Skirmish - cliffs, nav grid, crowd squads (the canonical RTS nav pattern)
3198
3433
  // Terrain: hand-drawn tier map (TerrainHeightsFromTiers) - north plateau, south flat,
3199
3434
  // two ramps. Cliffs become blocked nav EDGES, ramp tiles stay open.
3200
- // Nav: ONE authored source (the tier map); the grid derives from it with the canonical
3201
- // load order: collider raster -> FromTerrain -> EdgesFromTerrain -> BakeCrowdObstacles
3202
- // -> TileGridIsReachable + RaiseWorldError (a mis-built map fails at load, not mid-game).
3203
- // Units: `crowd` bodies routed by the shared grid - FindPath8 once per order,
3204
- // MoveAlongTilePathContinuous every frame (avoidance shapes the velocity: global path
3205
- // + local separation). Each unit drives ITSELF in its Update; orders arrive via fields.
3435
+ // Nav: ONE authored source (the tier map); this DENSE TileGridCreate grid builds with:
3436
+ // SetWalkableFromStaticColliders -> SetWalkableFromTerrain -> SetEdgesFromTerrain ->
3437
+ // BakeCrowdObstacles -> TileGridIsReachable + RaiseWorldError (fail at load, not mid-game).
3438
+ // (Sparse TileGridCreateFromTerrain city grids need no build chain; the bake is rejected there.)
3439
+ // Units: `crowd` bodies routed by the shared grid, both movement lanes shown:
3440
+ // GROUP orders (green squad, right-click) = ONE flow field per order +
3441
+ // MoveAlongTileFlowContinuous per frame - the squad converges and PACKS into a tight
3442
+ // blob at the goal (units finish by touching squadmates already parked there).
3443
+ // SOLO orders (red patrol) = FindPath8 once + MoveAlongTilePathContinuous per frame.
3444
+ // Each unit drives ITSELF in its Update; orders arrive via fields.
3206
3445
  // Player: spectator + top-down camera; right-click orders the green squad. Red squad
3207
3446
  // patrols the plateau and both sides auto-attack in range.
3208
3447
  // Buildings: a tower placed AFTER the raster stamps its footprint incrementally
@@ -3215,7 +3454,10 @@ alias model.wall
3215
3454
 
3216
3455
  #class Battlefield
3217
3456
  var Grid: List<Number> = []
3218
- var OriginT = Vector(-32, -32, 0) // grid origin in TILE units; world = tile * TileSize
3457
+ var OriginT = Vector(-32, -32, 0) // creation-only: the grid stores it; every call speaks WORLD tiles
3458
+ // Green squad's current group order: one shared flow field per right-click.
3459
+ var OrderField: List = []
3460
+ var OrderGoals: List<Number> = []
3219
3461
  var GreenAlive = 0
3220
3462
  var RedAlive = 0
3221
3463
  var Banner: Object
@@ -3255,22 +3497,22 @@ event OnSpawned {
3255
3497
  }
3256
3498
 
3257
3499
  // The canonical nav build. 64x64 edge-blocking grid over the terrain footprint.
3258
- Grid = TileGridCreate(64, 64, true, true)
3259
- TileGridSetWalkableFromStaticColliders(Grid, OriginT, 20, 200) // resets every tile; FIRST
3260
- TileGridSetWalkableFromTerrain(Grid, OriginT, 0.45) // steep + void (subtract)
3261
- TileGridSetEdgesFromTerrain(Grid, OriginT, 100) // cliffs = edges; TierCm/3
3262
- TileGridBakeCrowdObstacles(Grid, OriginT) // avoidance sees the same walls
3500
+ Grid = TileGridCreate(64, 64, OriginT, true, true)
3501
+ TileGridSetWalkableFromStaticColliders(Grid, 20, 200) // resets every tile; FIRST
3502
+ TileGridSetWalkableFromTerrain(Grid, 0.45) // steep + void (subtract)
3503
+ TileGridSetEdgesFromTerrain(Grid, 100) // cliffs = edges; TierCm/3
3504
+ TileGridBakeCrowdObstacles(Grid) // avoidance sees the same walls
3263
3505
 
3264
3506
  // Fail-loud map asserts: both squads' ground must connect via the ramps.
3265
- if !TileGridIsReachable(Grid, 32, 8, 32, 56) {
3507
+ if !TileGridIsReachable(Grid, 0, -24, 0, 24) {
3266
3508
  RaiseWorldError("south flat cannot reach the north plateau - ramp sealed")
3267
3509
  }
3268
3510
 
3269
3511
  // Incremental placement: a watchtower on the south flat. StampObject blocks its
3270
3512
  // footprint tiles without re-rastering; the seal guard would catch a bad spot.
3271
3513
  var t = SpawnAtTile(SavedObject.Watchtower, Vector(-2, -12, 0))
3272
- TileGridStampObject(Grid, OriginT, t, true)
3273
- if !TileGridIsReachable(Grid, 32, 8, 32, 56) {
3514
+ TileGridStampObject(Grid, t, true)
3515
+ if !TileGridIsReachable(Grid, 0, -24, 0, 24) {
3274
3516
  RaiseWorldError("tower placement sealed the map - unstamp or move it")
3275
3517
  }
3276
3518
 
@@ -3294,8 +3536,9 @@ var Team = 0
3294
3536
  var Hp = 30
3295
3537
  var Path: List<Number> = []
3296
3538
  var HasOrder = false
3297
- var LastGoalX = -1
3298
- var LastGoalY = -1
3539
+ var Marching = false // green group order: walking the shared OrderField
3540
+ var LastGoalX = -9999 // sentinel outside any world tile
3541
+ var LastGoalY = -9999
3299
3542
  var Speed = 260 // cm/s
3300
3543
  var AttackTimer: Timer
3301
3544
  var PatrolFlip = false
@@ -3327,22 +3570,28 @@ func OrderMoveToTile(goalX: Number, goalY: Number) {
3327
3570
  }
3328
3571
 
3329
3572
  func Manager() -> Battlefield { return (Scene.Manager0 as Battlefield) }
3330
- func MyTileX() -> Number { return WorldToTile(GetPosition()).X - Manager().OriginT.X }
3331
- func MyTileY() -> Number { return WorldToTile(GetPosition()).Y - Manager().OriginT.Y }
3573
+ func MyTileX() -> Number { return WorldToTile(GetPosition()).X }
3574
+ func MyTileY() -> Number { return WorldToTile(GetPosition()).Y }
3332
3575
 
3333
3576
  event Update {
3334
3577
  var bf = Manager()
3335
3578
  // Walk: one call per frame; avoidance shapes it (crowd body + continuous mover).
3336
- if HasOrder {
3579
+ if Marching {
3580
+ // Group lane: the shared field is every path to the click; done = reached
3581
+ // the goal OR packed against a squadmate already parked on it.
3582
+ if MoveAlongTileFlowContinuous(bf.OrderField, bf.Grid, Speed, 90, 360) {
3583
+ Marching = false
3584
+ }
3585
+ } else if HasOrder {
3337
3586
  // ArriveWithinCm 90: a squadmate resting near the goal must not stall the
3338
3587
  // arrival (avoidance stops dead-ahead; generous arrive completes the order).
3339
- if MoveAlongTilePathContinuous(Path, bf.Grid, bf.OriginT, Speed, 90, 360) {
3588
+ if MoveAlongTilePathContinuous(Path, bf.Grid, Speed, 90, 360) {
3340
3589
  HasOrder = false
3341
3590
  if Team == 1 { PatrolFlip = not PatrolFlip } // red: bounce between posts
3342
3591
  }
3343
3592
  } else if Team == 1 {
3344
3593
  // Red squad patrols the plateau rim between two posts.
3345
- if PatrolFlip { OrderMoveToTile(20, 52) } else { OrderMoveToTile(44, 52) }
3594
+ if PatrolFlip { OrderMoveToTile(-12, 20) } else { OrderMoveToTile(12, 20) }
3346
3595
  }
3347
3596
 
3348
3597
  // Auto-attack: nearest enemy trooper within 500cm, 1 hit/s.
@@ -3421,14 +3670,17 @@ event RightMouseClickByThisPlayer {
3421
3670
  if !p.IsValid() { return }
3422
3671
  var bf = (Scene.Manager0 as Battlefield)
3423
3672
  var t = WorldToTile(p)
3424
- var slot = 0
3673
+ // ONE flow field per group order - every path to the shared goal, reused by
3674
+ // stragglers and reinforcements. Movers finish by reaching the goal or by
3675
+ // PACKING against squadmates already parked on it, so the squad settles as
3676
+ // one tight blob: no leader, no per-unit paths, no target spread.
3677
+ bf.OrderGoals.Clear()
3678
+ bf.OrderGoals.Add(t.X.Clamp(-32, 31))
3679
+ bf.OrderGoals.Add(t.Y.Clamp(-32, 31))
3680
+ bf.OrderGoals.Add(0)
3681
+ bf.OrderField = TileGridFlowFieldBuild(bf.Grid, bf.OrderGoals)
3425
3682
  for u in GetAllObjectsWithSavedType(SavedObject.GreenTrooper) {
3426
- // 2-tile goal spacing: adjacent-tile goals let arriving units body-block each
3427
- // other (no rest-occupancy yet); a spaced 2x2 formation settles cleanly.
3428
- (u as Trooper).OrderMoveToTile(
3429
- (t.X - bf.OriginT.X + (slot % 2) * 2).Clamp(0, 63),
3430
- (t.Y - bf.OriginT.Y + (slot / 2).RoundDown() * 2).Clamp(0, 63))
3431
- slot = slot + 1
3683
+ (u as Trooper).Marching = true
3432
3684
  }
3433
3685
  }
3434
3686
  #end
@@ -5019,12 +5271,80 @@ screenCanvas TutorialUI {
5019
5271
  </example_vampire_survivor>
5020
5272
 
5021
5273
 
5274
+ <example_vehicle_driving>
5275
+ // Drivable car with an NPC driver you can carjack - the seat primitive end-to-end.
5276
+ // SeatPlayerAt works on ANY object (no mesh anchors needed): the engine suspends the
5277
+ // rider's capsule + native controls, the avatar rides the seat, and the default chase
5278
+ // camera follows - no per-frame camera or position writes. Car feel (accel/drag/turn)
5279
+ // stays script-owned kinematic velocity integration; seat roles and jack rules are
5280
+ // script vars. Meshed vehicles: author a socket("driver", ...) and use SeatPlayer.
5281
+
5282
+ #class CarController
5283
+ var Vel = Vector(0, 0, 0)
5284
+ var TopSpeed = 900
5285
+ var SeatOffset = Vector(0, 0, 55) // the car owns its seat geometry - never duplicate it per caller
5286
+ func Board(P: Object) {
5287
+ SeatPlayerAt(P, "driver", SeatOffset)
5288
+ }
5289
+ event Update {
5290
+ var driver = SeatOccupant("driver")
5291
+ if driver.Exists() {
5292
+ // Camera-relative stick -> arcade acceleration (bots: their held AI stick).
5293
+ Vel = Vel + driver.GetMoveVectorCamera() * 2200 * DeltaSecs()
5294
+ }
5295
+ Vel = Vel * 0.97 // drag; also rolls the car to a stop when empty
5296
+ if Vel.Length() > TopSpeed { Vel = Vel.GetNormal() * TopSpeed }
5297
+ MoveWithVelocityContinuously(Vector(Vel.X, Vel.Y, 0))
5298
+ if Vel.Length() > 60 { LookAtHorizontal(GetPosition() + Vel) }
5299
+ }
5300
+ #end
5301
+
5302
+ #class CityManager
5303
+ event OnSpawned {
5304
+ SetVisible(false)
5305
+ var cabbie = SpawnAIPlayer("Cabbie")
5306
+ (Scene.Taxi as CarController).Board(cabbie)
5307
+ cabbie.SetAIMoveVector(Vector(0.55, 0.25, 0)) // held stick: the car reads it and cruises
5308
+ }
5309
+ #end
5310
+
5311
+ #class player
5312
+ event ActionDownByThisPlayer[Interact] {
5313
+ var car = Scene.Taxi
5314
+ if SeatedIn().Exists() {
5315
+ // Step out beside the car (UnseatPlayer restores physics in place).
5316
+ car.UnseatPlayer(Self)
5317
+ TeleportTo(car.GetPosition() + Vector(150, 0, 5))
5318
+ return
5319
+ }
5320
+ if DistanceTo(car) > 350 { return }
5321
+ var occupant = car.SeatOccupant("driver")
5322
+ if occupant.Exists() and occupant.IsAIPlayer() {
5323
+ // Carjack: pull the NPC out, it flees on foot.
5324
+ car.UnseatPlayer(occupant)
5325
+ occupant.TeleportTo(car.GetPosition() + Vector(-150, 0, 5))
5326
+ occupant.SetAIMoveVector(Vector(-1, 0, 0))
5327
+ }
5328
+ (car as CarController).Board(Self)
5329
+ }
5330
+ #end
5331
+
5332
+ material Asphalt { base = "PolishedConcrete" }
5333
+ object ground { model = "cube" material = "Asphalt" position = (0, 0, -100) size = (12000, 12000, 100) }
5334
+ object Taxi : CarController { model = "cube" position = (300, 0, 0) size = (90, 180, 60) color = (0.95, 0.75, 0.1) }
5335
+ display manager : CityManager { visible = false }
5336
+ screenCanvas HUD { textLabel T { offset = (20, 20) size = (620, 28) align = (left, center) text = "E near the taxi: carjack the cabbie. Drive with WASD; E again to step out." color = (1, 1, 1) } }
5337
+
5338
+ </example_vehicle_driving>
5339
+
5340
+
5022
5341
 
5023
5342
  </world_description_examples>
5024
5343
 
5025
5344
  <updating_old_worlds>
5026
- Worlds may carry `#doc_version N` = the doc version they were last authored/upgraded against (current: 2; absent = 0). When editing a world, apply every entry below whose version exceeds its N, then set the line to `#doc_version 2` (stage_new_world stamps new worlds automatically). Weave1-era calls have their own section: <weave2_migration>.
5345
+ Worlds may carry `#doc_version N` = the doc version they were last authored/upgraded against (current: 3; absent = 0). When editing a world, apply every entry below whose version exceeds its N, then set the line to `#doc_version 3` (stage_new_world stamps new worlds automatically). Weave1-era calls have their own section: <weave2_migration>.
5027
5346
  - v2: Grade values near 0 (SetContrast/SetSaturation/SetSkyboxContrast/SetAssetSubmeshContrast): pre-multiplier leftovers meaning "unchanged" - rewrite v -> 1+v (neutral = 1) unless flat gray/grayscale is clearly the intent.
5347
+ - v3: TileGrid calls went world-tile (E10b). Creations gained an OriginTile param (`TileGridCreate`/`Create3D`; `CreateFromTerrain` already had one) and every OTHER grid/follower call dropped it: delete the OriginTile arg and pass world tiles - `WorldToTile(pos)` feeds straight in, so also delete any `- OriginT` offset math. `TileGridSetWalkableFromTerrainRect`/`TileGridSetEdgesFromTerrainRect` collapsed into `TileGridRefreshFromTerrainRect(Grid, X1, Y1, X2, Y2)` (same drained world-cm quads; drop the MaxSlope01/MaxStepCm args - one call refreshes both planes). `SpawnTerrainRecipe`'s CellSize arg is now TileSizeCm and the unnamed form may omit it (defaults to the nav TileSize, 100). tileMap `cellSize` was renamed `tileSize`.
5028
5348
  </updating_old_worlds>
5029
5349
 
5030
5350
  <additional_notes>
@@ -5059,7 +5379,7 @@ Weave2 is the only engine: a world with '#set Weave2 0' at the top refuses to lo
5059
5379
  - Never work around a missing or retired simcall by defining a local func with its name (e.g. an empty 'func SetCastShadow(enabled: Bool) { }'): it silently disables engine behavior and breaks again when the engine call changes. Migrate the call site, or stop and report the gap.
5060
5380
  - Over-time controllers (MoveToOverTime etc.) and Move*Continuously directional variants are retired: drive movement from an Update loop or MoveInDirectionContinuously.
5061
5381
  - TeleportUp/Down/Left/Right/Forward/Backward, TeleportCenterTo, TeleportOnTopOf, TeleportGroupWithPivot are retired: use TeleportTo with a computed Position, e.g. TeleportTo(GetPosition().ShiftUp(300)) or TeleportTo(Base.GetPosition().ShiftUp(Base.GetHeight())).
5062
- - SetDisplayPosition/SetDisplayCenterPosition are legacy and DisplayObject-only (worldCanvas tags etc.); on any other receiver they hard-error at spawn. Prefer SetPosition/SetCenterPosition (any object); for a visual-only offset that leaves the collider in place, use SetRenderOffset.
5382
+ - SetDisplayPosition/SetDisplayCenterPosition are legacy: DisplayObjects and editor-only scene objects (canvas tags, effects, decals, lights) accept them, any other receiver runtime-errors at the call. Prefer SetPosition/SetCenterPosition (any object); for a visual-only offset that leaves the collider in place, use SetRenderOffset.
5063
5383
  - Untyped 'var X: List' now infers element type; annotate 'List<T>' if inference errors (W2C0620/W2C0621). Lists can now hold Vector/Position/Timer/class types directly (un-unroll old parallel-array workarounds).
5064
5384
  - A migration pass must be behavior-preserving: port call sites 1:1 and verify, then do architecture cleanups (pooling, query changes, debug removal) as a separate pass.
5065
5385
  </weave2_migration>