oriverse-engine 0.1.10 → 0.1.12

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,17 +1,18 @@
1
1
 
2
2
  >>>>>>>>>>>>
3
3
  Weave World Description Language Documentation
4
- Version:31Aug26-00:34-35561
4
+ Version:07Sep26-20:39-36010
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`, `walker`, `display`, `region`, and lights `pointLight`/`spotLight`/`areaLight` — describe what exists in the world.
10
+ - scene declarations: `object`, `saved`, `physics`, `crowd`, `climbingCrowd`, `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 (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>).
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), #svg (SVG-subset drawing rasterized at load to `baked.<Name>.color` with alpha - icons, HUD art, logos, signs; auto-creates a cutout uv `material <Name>`; header `size=N` = long side px, short side follows the viewBox; body authoring in the svg_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>), #fieldmap (persistent GPU canvas stamped from scripts - trails/footprints/scorch; see <field_maps>), #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>), #artstyle "<technique, palette, linework, mood>" (subject-free; inherited by every image/flipbook gen call and search_assets ranking).
13
+ - For Enhanced (EEVEE-style) rendering, edit the top-level `#set EnhancedRendering 1` through the normal Weave apply flow; `0` or absent keeps the original Fast renderer. Device availability still applies; this is display-only and needs no menu selection.
13
14
  - Scene numeric fields support constant arithmetic: `+`, `-`, `*`, `/`, unary sign, parentheses.
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
+ - One statement per line; separate statements on the same line with `;`. A statement ends at the line break: wrap after a trailing operator or comma (call args and list literals may span lines); a line starting with `-` is a new statement (unary minus), never a continuation. An empty list literal `[]` needs a List-typed context, e.g. `var H: List<Number> = []`.
15
16
  - 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
17
  - No recursion: direct or mutual func call cycles are a compile error; rewrite as loops (a List can serve as an explicit stack).
17
18
  - 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)`).
@@ -23,7 +24,7 @@ Weave World Description Language Documentation
23
24
  - 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.
24
25
  - Object size clamps per axis at load: 500m static-collidable, 100m movable-physics, 1km with collision off — tile bigger floors/walls from 500m pieces.
25
26
  - Grass on objects: static mesh `object`/`display` clauses accept grass = true (blades on upward faces; steep faces stay bare; display-only, no collision; global SetGrass* look knobs apply; skinned models grow nothing). On model = "cube" blades cover the top face (yaw rotation only) and grass_border = round|noise (edge falloff, default full face) + grass_border_noise_scale = 220 (cm, noise feature size) shape the edge.
26
- - Prefer `height = ...` for imported/custom models to preserve proportions; use `ScaleToHeight(...)` at runtime; use `size = ...` only when intentionally stretching X/Y/Z.
27
+ - Prefer `height = ...` for imported/custom models to preserve proportions; use `ScaleToHeight(...)` at runtime; use `size = ...` only when intentionally stretching X/Y/Z. Without either, a market model keeps its upload height — often toy-scale; set `height` from a real-world target (car ~150, sofa ~80).
27
28
  - "world description" = the serialized text of a world (scene + classes); saved from the editor and pasted back to reload. AI builds these; users paste them in.
28
29
  - `#class player` is auto-attached to every PlayerObject. Do not declare the player in scene. Declaring it REPLACES the built-in default player: call SetDefaultPlayerControlsEnabled(true) in its OnSpawned to keep built-in WASD move + jump, or code a movement loop — with neither, players cannot move (see <camera>).
29
30
  </weave_language>
@@ -44,24 +45,25 @@ Weave World Description Language Documentation
44
45
  - 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.
45
46
  - 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.
46
47
  - You usually do NOT know real ori urls. Prefer typed alias placeholders and let the system/human bind real assets later:
47
- - All four types may be referenced UNDECLARED or declared bare (`alias sound.hit`, no `=` target); they resolve from the generated libraries below, show a safe placeholder until generation lands (cube / default explosion / placeholder sprite / placeholder clip), and a human can rebind them.
48
- - MODEL aliases resolve from a generated-mesh library: a bare declaration `alias model.tree` (no `=` target, auto-added for undeclared refs too) binds a matching generated mesh by name at load; on a miss it shows a placeholder (cube; the player a mannequin stand-in) while a mesh is generated in the background, swapping in live when it lands. Name model aliases with plain singular nouns (`model.tree`, `model.market_stall`, not `model.trees`/`model.bigTreeA`) so library matching works. Want a specific existing mesh to start from or adapt -> the `mesh_library_search` tool returns library `#mesh` source to inline. A bound word's source is already in the world text (`#mesh gen_<word>` + its `material`/`#texture` clauses) — recolor/restyle by editing those directly. Character-noun words mint as rigged humanoids that auto-play idle/walk/run; the alias status marks minted words `rigged humanoid`.
48
+ - All four types may be referenced UNDECLARED or declared bare (`alias sound.hit`, no `=` target); they resolve from the libraries below, show a placeholder when missing (cube / default explosion / placeholder sprite / placeholder clip), and a human can rebind them. Generation availability depends on the asset type.
49
+ - MODEL aliases select existing library models. A missing model remains a placeholder; adapt a library mesh, author a local `#mesh`, or select an existing marketplace asset. `mesh_library_search` retrieves editable source; `use_mesh_asset` imports an exact pick. Save useful new or adapted meshes with `save_mesh_asset`. Check rig and clip metadata before choosing a character model.
49
50
  - IMAGE aliases resolve the same way from a generated-UI-image library: bare `alias image.gold_icon` binds a generated icon by name at load; on a miss it shows a placeholder sprite while an icon is generated in the background. Prefer bare image aliases for UI icons; name them by subject (`image.gold_icon`, `image.energy_icon`).
50
51
  - SOUND aliases resolve the same way from a generated-sound library: bare `alias sound.coin_pickup` binds a generated clip by name at load; on a miss it plays a placeholder clip while a sound is generated in the background. Name sound aliases by the event they accompany (`sound.coin_pickup`, `sound.sword_hit`, `sound.door_creak`) so generation prompts come out right.
51
52
  - ANIMATION: referencing an undefined `Ori.anim.canon.<verb>` appends the canonical-clip library's `#clip <verb> on canon` block at load (plain action verbs: `anim.canon.cheer`, `anim.canon.stomp`); it plays on any rigged humanoid via engine retarget. On a library miss the character keeps its current animation; the verb is queued for house minting and binds on a later load. A world-authored `#clip <verb> on canon` always wins.
52
53
  - EFFECT aliases resolve the same way from a generated-effect library: bare `alias effect.sparkle_burst` binds a generated `#particles` block by name at load; on a miss it shows a default explosion while an effect is generated in the background. Name effect aliases by visual (`effect.sparkle_burst`, `effect.heal_glow`, `effect.smoke_puff`).
53
54
  - Never invent a fake `ori.*` url: either leave a typed alias undeclared / bare (gets resolved or a placeholder) or declare it with a real `ori.*` url you actually know.
55
+ - The word is your handle for the slot; the library binds by MEANING: word + `desc="..."` + the object's class and `size` + `#artstyle` pick the closest existing asset of that kind under any library word in the `#artstyle` look (name one of low_poly [default], painterly, realistic, pixel, anime, voxel, cartoon, watercolor, clay in the string). Missing models need an explicit choice or local mesh; other types use their supported generation lanes. Add `desc=` where the bare word under-specifies (`alias model.bear desc="huge scarred grizzly boss"`); `size=(x,y,z)` cm on model, `size=(w,h)` UI base units on image.
54
56
  - Reference aliases as strings in scene fields: `model = "alias.model.goblin"`, `effect = "alias.effect.explosion"`.
55
57
  - Reference aliases in Weave code through `Ori.alias.<type>.<name>`: `SetModel(Ori.alias.model.goblin)`, `SpawnEffect(Ori.alias.effect.explosion, GetPosition())`, `SetUiImage(Ori.alias.image.gold_icon)`.
56
58
  - Reuse ONE alias per semantic asset everywhere, so rebinding one declaration swaps every usage at once (e.g. every enemy uses `alias.model.goblin`). Keep aliases world-local; never chain one alias to another.
57
59
  </asset_aliases>
58
60
 
59
61
  <asset_word_library>
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).
62
+ - 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 can match existing assets by meaning. A missing model needs a local mesh or another selection; other types use their supported generation lanes. Flipbooks are authored via flipbook_gen.
61
63
  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
64
+ image: acid_slime acid_slime_m acid_slime_s acolyte airtank_icon ak_rifle_icon alien_bug angled_player_banner aorta art_aoe art_dodge art_ember art_empower art_evade art_flame art_fury art_guard art_heavy art_hex art_insight art_mend art_multi art_slash art_sweep art_venom art_ward ascended_one automaton awakened_one axe_icon banana_weapon_icon bandage_icon bandit banshee barracks_icon baseball_bat_icon bastion bean_icon bean_seeds_icon bear bee_swarm berries_icon berry_icon blue_slaver bone_icon book_icon book_of_stabbing boss_skull bread_icon bullet_icon byrd 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 centurion champ cheese_icon chest_icon chosen chrono_wisp cinder_fiend clock_icon clockwork_hound coconut_icon coin_icon cooked_meat_icon cooked_meat_icon_v2 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 fiber_icon fiber_plant_icon_v2 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 frail_icon fungi_beast fungi_sporeling fungus gnawing_mass goblin goblin_brute gold_coin_icon gold_icon gold_icon_2 gourd_icon green_louse gremlin gremlin_fat gremlin_leader gremlin_nob gremlin_sneaky gremlin_wizard grenade_launcher_icon guardian 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 icon_compass impact_fx intent_attack intent_attack_debuff intent_block intent_buff intent_curse intent_debuff intent_sleep intent_unknown inverse_trapezoid_top_bar iron_ore_icon ironclad jaw_worm jawworm jungle_berry_icon_v2 knife_icon knight_helmet lagavulin laser_gun_icon leaf_icon lich loading_splash log_icon longodds_hall longodds_table looter louse lurker machine_gun_icon mackerel_icon mad_gremlin maw merc_portrait metal_cast_icon mold_icon monster_head moon_icon mystery_icon mystic nemesis ninja_runner nob node_boss node_elite node_fight node_rest node_shop obsidian_icon obsidian_sentry oozeslime orange_cat_party_portrait orb_walker orc panel_frame parasite parsnip_icon parsnip_seeds_icon pickaxe_icon pill_bottle_icon pistol_icon plank_icon player_debtor player_gambler player_hero poison_icon potato_icon potato_seeds_icon power_icon purple_hive_mind_insect_warrior_wide_splash qa_probe_gem radish_icon raw_meat_icon raw_meat_icon_v2 red_louse red_slaver refinery_icon regen_icon relic_chalice relic_core relic_gauntlet relic_lantern relic_oathplate relic_serpent 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 shellwarden shield_gremlin shotgun_icon silent skeleton skeleton_archer skill_dagger_icon skill_fireaura_icon skill_hammer_icon skill_shield_icon skill_stealth_icon slash_fx slaver slime_boss slime_horror snake_dagger snapper_icon sneaky_gremlin snecko snecko_hatchling sniper_rifle_icon snowflake_spin soda_can_icon spear_icon spear_icon_v2 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 thorns_icon time_eater torch_icon torch_icon_v2 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 vagrant vampire ventricle vulnerable_icon warden water_bottle_icon water_drop_icon water_gourd_icon_v2 watering_can_icon weak_icon werewolf white_chicken_party_portrait witch withered_titan wolf wood_icon yokai_mask yokai_samurai_oni_wide_splash
65
+ 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 blockup body_thump body_thump_2 book_open 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 click 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 deep_hum deny dice_rattle distant_groan dodge dodge_dust dodge_dust_2 dog_bark dog_pistol door_close door_creak door_creak_long door_open door_open_2 door_unlock dread_horn dry_swallow dual_hit earthquake_rumble earthquake_rumble_2 electric_burst electric_shock elevator_ding 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 fluorescent_buzz foe_die foe_turn fog_horn food_complete food_complete_2 found_chime front_weapon_activation fusion_burst game_check game_end game_start gameplay_music gameplay_music_2 generator_start gentle_wind_chime 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 horror_sting item_drop item_pickup item_pickup_2 jet_engine knell knife_hit 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 lose 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 page_turn pickup piece_capture piece_lock piece_move pistol_reload pistol_reload_2 pistol_shot pistol_shot_2 place player_breath player_hit player_hurt player_hurt_2 police_siren punch punch_2 rain_ambience raptor_step ready_up rear_ability_activation refuse 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 shield_clang ship_destroyed ship_horn shotgun_shot signal_ping siren slab_turn slap slap_hit slash soft_wind_chime speed_burst stone_rotate storm_thunder survivor_step sword_clang sword_clang_2 sword_hit sword_hit_2 sword_swing tap tire_skid toast_pop toy_pop trex_step triumph 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_drip water_splash wave_crash wave_start weapon_reload weapon_switch whistle win wind_gust wind_gust_2 wind_howl wolf_howl wood_impact wrong_buzz wrong_pick zebrite_chime zombie_attack zombie_growl zombie_roar
66
+ effect: acid_splash arcane_cast arcane_impact arrow_hit aurora barrier_impact basic_hit_impact basic_sword_slash bat_projectile bite_hit bite_splash black_hole blaster_laser blaster_laser_impact blessing_burst blood_ground_splatter blood_hit blood_hit_splash blood_splash blue_plasma_ball blue_plasma_impact boost_burst brief_smoke_puff bullet_blood_splash bullet_impact bullet_trail burner burner_2 burner_3 burner_4 burner_5 burner_6 burner_7 burner_8 burner_9 campfire candle_flame cannon_blast capture_burst cartoon_impact cash_sparkle cash_sparkle_2 charm_aura chest_item_shine chest_top_shine coin_sparkle collapse_dust core_pickup crash_burst critical_smoke cute_star_pickup_burst damage_smoke damage_sparks damage_sparks_2 damage_sparks_3 dash_trail dash_trail_2 dense_ripples dismember dodge_dust dropped_item_marker dust_puff dust_puff_2 dust_puff_3 electric_ball_impact electric_ball_projectile electric_burst electric_overheat_shock electric_sparks elemental_burst eliminated_burst enemy_hit energy_bolt energy_charge environmental_electric_sparks evolution_burst explosion explosion_burst falling_green_petals falling_sakura_petals falling_snow finish_confetti fire_burst fire_impact_realistic fire_impact_stylized fireball fireball_burst fireball_projectile fireflies firework flamethrower_stream flower_area_sparkle flower_burst flying_birds forward_dash_lines forward_muzzle_flash ghost_mist god_rays gold_money_obtained gold_rush golf_cup golf_hit green_circular_area grenade_blast ground_click_marker ground_hologram ground_impact_stylized ground_ping_alert ground_smoke_impact gun_hit_ring_stylized gun_muzzle_flash hammer_impact heal_glow healing_aura healing_burst healing_glow healing_potion_activation heavy_critical_impact heavy_divine_impact heavy_lightning_ground_impact heavy_lightning_strike hit_burst hit_burst_2 hook_hit hook_hit_2 ice_shard ice_smoke_ground_impact impact_flash_critical impact_flash_small impact_shockwave impact_shockwave_2 impact_shockwave_3 impact_sparks impact_sparks_2 ink_cloud item_glow jump_smoke_realistic jump_smoke_stylized large_area_ground_fog large_cloud large_waterfall laser_beam laser_beam_muzzle lava_eruption level_up_burst level_up_light_beam lightball_charge lightball_projectile lightning_burst lightning_strike lightning_strike_impact lingering_smoke_cloud magic_ball_projectile magic_blast magic_ring magic_sparkle magic_vortex magic_wand_impact mana_potion_activation mega_flameball metal_hit meteor_fire meteor_ground_impact mine_burst missile_smoke_trail muzzle_flash muzzle_flash_2 neutral_circular_area objective_pickup orbital_laser orbital_laser_impact overheat_smoke parry_impact player_hit poison_ball_impact poison_ball_projectile poison_cloud poison_ground_area punch_impact radiant_impact_burst rage_burst rainbow_burst rainbow_burst_2 rainbow_trail rapid_slash red_circular_area red_eye_flash repair_sparks respawn_burst respawn_burst_2 revive_burst revive_glow rickshaw_burn ringout_explosion rocket_explosion rot_pulse rotten_smoke_aura rune_pickup saliva_drip sci_fi_item_spawn shallow_water_caustics shield_burst shield_pop single_ripple sky_rainbow sky_stars slap_hit slash_burst slash_burst_2 slash_burst_3 small_explosion small_waterfall smoke smoke_heavy_explosion smoke_puff smoke_puff_2 snow_aura snowball_impact snowball_projectile soft_holy_light_impact sonar_pulse spark_bomb_fuse spark_impact sparkle_burst sparkle_burst_2 sparkle_burst_3 sparkler speed_burst spinning_sword_wind_wave spotlight steam_burst step_dust stun_aura stun_stars stun_stars_2 stun_stars_3 sword_hit sword_impact sword_slash tank_burning tank_explosion terrain_blast thruster_flame top_collision_impact torch_flame toxic_ground_impact toxic_rain_trail volcano_smoke w_firethrowe w_firethrust_finale_2026 walking_puff_stylized warp_arrival warp_departure warp_gate warp_ground_residue water_bubbles water_burst water_fountain water_ground_impact water_splash water_splash_realistic water_splash_stylized waterball_projectile wind_burst wind_impact wind_leaves windball_projectile
65
67
  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
66
68
  </asset_word_library>
67
69
 
@@ -70,30 +72,31 @@ flipbook: blood_fine blood_mist blue_flame bubble_float dust_fine_grey dust_puff
70
72
  - `#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")`).
71
73
  - 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;`.
72
74
  - 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.
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.
75
+ - 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, White, 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. MetalDark is a cool mid-grey (#75808B), not near-black - for black metal declare `material Iron { base = "MetalDetail" tint = "#1a1c1f" }`.
74
76
  - 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.
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 }`.
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
+ Material clauses also accept `emission = N` (self-lit glow in the material's tinted color; linear: 0.3-0.5 = tinted glow that keeps its colour, 1 = as bright as a sunlit white surface, 2 = pastel, 4+ reads white (bloom carries the tint), 8+ only for sub-pixel LEDs / sun discs) and `roughness = 0..1` / `metallic = 0..1` (uniform overrides of the baked surface response) — e.g. `material CyanGlow { base = "MetalDetail" tint = "#19D9FF" emission = 1.5 }`. Opaque materials also accept `lighting = "snow"`: engine snow shading (soft wrapped terminator, low-sun backscatter, distance-faded ice glints; built-in Snow carries it already) for snowy ground/drift materials. `snow_cover = "off"` keeps a material bare under SetSnowCover (interior props, signs).
78
+ - 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; a model's own imported maps are reusable without re-upload: `material X { shader = "..." textures = "model.<creator>.<name>" }` fills tex0/1/2 (basecolor/normal/ORM) — inspect_model lists each model's maps with per-slot urls. `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
79
  - 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.
78
80
  - 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.
79
81
  - 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).
80
82
  - `#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));`).
81
83
  - 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
84
  - 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.
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.
85
+ - `#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), `path = "M x,y L ... C ... Z", classIndex;` (repeatable: SVG path data in absolute cm with page-up = +Y; every vertex becomes a node, vertices within 1 cm of an existing node merge into junctions, L/H/V segments become edges, C/Q/S/T/A segments become `curve` edges, `Z` closes to the start - draw a street plan as one drawing with junction points shared between paths; editor saves normalize it into nodes/edges/curve statements). 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.
84
86
  - `#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.
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.
87
+ - Emission is linear (0.3-0.5 = tinted glow that keeps its colour, 1 = as bright as a sunlit white surface, 2 = pastel, 4+ reads white (bloom carries the tint), 8+ only for sub-pixel LEDs / sun discs): put it on small accents - lamps, signs, runes, windows; large emissive surfaces wash out the scene, keep those <= 1.
86
88
  - A surface still reading WHITE at emission 0/low is specular+bloom, not emission: glossy (low roughness) under a strong sun blooms to a white blob - fix with roughness 0.8+ (matte) and/or a darker tint; lowering emission further changes nothing.
87
89
  </texture_dsl_usage>
88
90
 
89
91
  <adjuster_usage>
90
92
  - `#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.
93
+ - Body lines: `section "Title"` groups knobs after it (cosmetic for scene knobs - the panel regroups them by engine family); `knob <SimCallName> = <value> [range a..b] [user]`; value is a number, `#rrggbb` for color knobs, or a listed word for choice knobs; `range` overrides the slider range of number knobs; a trailing `user` marks the line user-LOCKED (see below). `//` comments ok.
92
94
  - 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.
93
95
  - 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).
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.
96
+ - Any knob line can carry a trailing ` user` lock (the panel's lock icon; [user] lighting/grade knobs below gain it automatically when the USER tunes them): patches retuning, removing, or unlocking that LINE are rejected (their tune is sovereign), and a script Set* you add on a locked knob blocks submit - retune locked knobs only via the propose_adjust_values tool (an [Apply values] card; only the user's click applies). UNMARKED knob lines - [user] ones included, delegated mood work, 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.
97
+ - Relative nudges ("30% brighter", "shadows a bit lighter"): propose_adjust_deltas({"SetSunIntensity": "+30%"}) reads the live values and mints the same [Apply values] card - one call, no patch/playtest/submit round per nudge.
95
98
  - 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)
99
+ - 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), SetGrassBladeDensityPercent (10..100), SetGrassSpacingCm (0..100), SetGrassTerrainActiveDistanceM (0..500), SetGrassCullDistanceM (0..500), SetGrassFadeDistancePercent (0..100), SetGrassFarThinningPower (0..8), SetTerrainErosionDetailPercent (0..300), SetTerrainNormalStrengthPercent (0..300), SetTerrainDisplayExtension (0..8000), SetSunTilt (0..90), SetSunRotation (0..360), SetSunIntensity (0..20) [user], SetSunColor (color) [user], SetSunSpecularScale (0..3), SetSkyLightIntensity (0..8) [user], SetSkyLightColor (color) [user], SetSkyLightSaturation (0..3) [user], SetSkyboxCloudCoverage (0..1), SetSkyboxCloudDetail (0..3), SetSkyboxCloudScale (0..4), SetSkyboxCloudType (0..1), SetSkyboxExposure (0..3) [user], SetSkyboxSaturation (0..3) [user], SetSkyboxContrast (0..3) [user], SetSkyboxTint (color) [user], SetSkyboxHaze (0..8) [user], SetSkyboxGroundColor (color) [user], SetSkyboxStarIntensity (0..3), SetSkyboxColor (color) [user], SetSkyboxSunVisible (0/1), SetSkyboxHorizonTilt (-45..45), SetSkyboxRotation (0..360), SetSkyboxHue (-180..180), SetFogColor (color), SetFogStartDistance (0..2000), SetFogEndDistance (0..2000), SetFogSkyboxAffect (0..100), SetHeightFogEnabled (0/1), SetHeightFogBottom (-100..200), SetHeightFogTop (0..200), SetHeightFogSoftness (0..1), SetShadowDistance (0..500), SetShadowBlurRadius (0..20) [user], SetShadowOpacity (0..1) [user], SetShadowLift (0..3) [user], SetShadowLiftThreshold (0.02..0.3) [user], SetLocalShadowBlurRadius (0..64), SetShadowDepthBiasConstant (0..16), SetShadowDepthBiasSlopeScale (0..8), SetAllLocalShadowsEnabled (0/1), SetStaticLodShadowTransition1 (0..1), SetStaticLodShadowTransition2 (0..1), SetStaticLodShadowTransition3 (0..1), SetExposure (-3..3) [user], SetContrast (0..4) [user], SetSaturation (0..4) [user], SetBloomThreshold (0..3) [user], SetBloomIntensity (0..2) [user], SetBloomSoftKnee (0..1), SetBloomClampValue (0..16), SetSsaoIntensity (0..1) [user], SetSsaoDistance (0..10), SetSsrMode (Off|Low|High), SetSsrIntensity (0..5), SetSsrRoughnessBlurMultiplier (0..8), SetSsrRoughnessMipClampLower (0..8), SetSsrRoughnessMipClampUpper (0..8), SetSsrRoughnessFadeStart (0..1), SetSsrRoughnessFadeEnd (0..1), SetSsrRayFadeNear (0..8), SetSsrRayFadeFar (0..8), SetVolumetricLightIntensity (0..1000), SetVolumetricLightDensity (0..1000), SetVignetteStrength (0..1) [user], SetVignetteRadius (0..1) [user], SetVignetteSoftness (0..1) [user], SetVignetteColor (color) [user], SetWindDirectionDeg (0..360), SetWindStrength (0..1), SetSnowCover (0..1), SetWindGustiness (0..1), SetTreeLodSwitchDistanceM (0..300), SetTreeMeshDistanceM (0..1000), SetTreeCullDistanceM (0..3000), SetTreeFadeDistancePercent (5..80), SetTreeFarLeafDensityPercent (10..300), 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), SetWaterCausticIntensity (0..3), SetWaterReflectionStrength (0..2), SetWaterReflectionDistortion (0..2)
97
100
  Example:
98
101
  #adjuster
99
102
  section "Grass"
@@ -107,17 +110,17 @@ knob SetGrassHeightMaxCm = 45 range 0..120
107
110
  - 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
111
  - 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.
109
112
  - 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.
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.
113
+ - JSON values (strict JSON: negatives are `-0.82`, never `0 - 0.82`): 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.
111
114
  - 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).
112
115
  - 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
116
  - 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.
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).
117
+ - Spawn keys: `spawnStartDelay`, `spawnRate` per sec, `duration` seconds, `isLooping` (default `0` = one-shot: a finite `duration` stops emission, `0` streams `spawnRate` on but fires bursts once, and `SpawnEffectWithAutoDestroy` removes the effect after `spawnStartDelay + duration` + particle life; `1` = restart every `spawnStartDelay + duration`, `duration` `0` = never stop - set it on every continuous or repeating emitter), `burstParticleCount` (fires at t=0, then every `burstInterval` seconds while emitting; `burstInterval` unset = the t=0 burst only).
115
118
  - 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
119
  - 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).
117
120
  - 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).
118
121
  - 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`.
119
122
  - 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.
120
- - Mesh components (debris, coins, leaves, shards, casings): `"type": "mesh"` renders lit opaque 3D instances of a real mesh instead of sprites; layer with billboard/ribbon components in one effect (rock burst = mesh chunks + dust billboards + one flash). `"particleMeshName"`: a local `"mesh.Name"` or a bare-alias `"alias.model.<noun>"` (library-bound or background-generated; placeholder cube until it lands) — or an array of up to 4, picked per particle (identical chunks read fake). Meshes keep their own materials and receive scene lighting: `colorConstant`/`colorSequence` tints (omit = untinted), `opacityConstant`/`opacitySequence` dither-fades, `sizeConstant`/`sizeSequence` = largest mesh dimension in meters. `"meshAlign"`: `"tumble"` (default; `"tumbleSpeedRange": [min,max]` deg/s random-axis, default [90, 360]) | `"velocity"` (mesh +Z along travel: arrows, shards) | `"world"` (authored orientation). Emission/shape/motion keys keep their exact billboard meaning. Texture/flipbook keys, `orientation`, `rotationRange`/`rotationSpeedRange`, `squash*`, `additiveBlend`, `softParticleFade*`, `renderResolution` are load ERRORS on a mesh component. `"castShadow": 1` enables shadow casting (default off; a fading particle stops casting, like skinned deaths). `"groundBounce": 0..1` bounces world-space particles off terrain/static ground with that restitution and lets them settle until life ends (visual bounce, not physics; `lockedToEmitter` particles skip it). HDR `colorConstant`/`colorSequence` excess over 1.0 becomes emissive glow. Each particle is a real lit draw — keep `spawnRate * lifeTime` under ~100 and meshes low-poly.
123
+ - Mesh components (debris, coins, leaves, shards, casings): `"type": "mesh"` renders lit opaque 3D instances of a real mesh instead of sprites; layer with billboard/ribbon components in one effect (rock burst = mesh chunks + dust billboards + one flash). `"particleMeshName"`: a local `"mesh.Name"` or a bare-alias `"alias.model.<noun>"` (library-bound; an unresolved model displays a placeholder cube) — or an array of up to 4, picked per particle (identical chunks read fake). Meshes keep their own materials and receive scene lighting: `colorConstant`/`colorSequence` tints (omit = untinted), `opacityConstant`/`opacitySequence` dither-fades, `sizeConstant`/`sizeSequence` = largest mesh dimension in meters. `"meshAlign"`: `"tumble"` (default; `"tumbleSpeedRange": [min,max]` deg/s random-axis, default [90, 360]) | `"velocity"` (mesh +Z along travel: arrows, shards) | `"world"` (authored orientation). Emission/shape/motion keys keep their exact billboard meaning. Texture/flipbook keys, `orientation`, `rotationRange`/`rotationSpeedRange`, `squash*`, `additiveBlend`, `softParticleFade*`, `renderResolution` are load ERRORS on a mesh component. `"castShadow": 1` enables shadow casting (default off; a fading particle stops casting, like skinned deaths). `"groundBounce": 0..1` bounces world-space particles off terrain/static ground with that restitution and lets them settle until life ends (visual bounce, not physics; `lockedToEmitter` particles skip it). HDR `colorConstant`/`colorSequence` excess over 1.0 becomes emissive glow. Each particle is a real lit draw — keep `spawnRate * lifeTime` under ~100 and meshes low-poly.
121
124
  - Runtime-managed fields are not useful in JSON: `time`, `delta_time`, `world_matrix`, `world_matrix_inv`, `emitter_time`, `emitter_id`, `system_scale`, `instance_tint`, `instantClearOnRemove`, padding fields. Unknown keys only log warnings.
122
125
  Example:
123
126
  #texture SmokePuff
@@ -132,7 +135,7 @@ out.alpha = smoothstep(0.48, 0.18, d);
132
135
  #end
133
136
  #particles SmallSmoke
134
137
  [
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 }
138
+ { "particleTextureName": "texture.SmokePuff.color", "boundingBoxSize": [1, 1, 2], "boundingBoxCenter": [0, 0, 0.9], "renderResolution": "half", "spawnRate": 18, "isLooping": 1, "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 }
136
139
  ]
137
140
  #end
138
141
  effect ChimneySmoke {
@@ -157,29 +160,29 @@ out = normalize(biquad(lp, sweep, cutoff = 2200, q = 1.2) * env_adsr(0.005, 0.1,
157
160
 
158
161
  <mesh_dsl_usage>
159
162
  - `#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.
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.
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.
163
+ - `#mesh <Name> destructible: masonry|wood|rock|concrete [piece_cm]` 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()). The optional number is the target piece size in cm (50..=1000) - e.g. `destructible: concrete 150`; omitted, piece size auto-scales so the model lands near the chunk budget (~2000 pieces), and tiny thin parts (panes, trim) don't bake chunks. Bakes run lazily in the background (tagging many/large meshes is fine). Preview with the preview_destruction tool; destructible mesh names cap at 46 chars.
164
+ - 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 an existing library mesh at load; when none fits, adapt or author 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.
165
+ - `#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 (import/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.
163
166
  - 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.
164
167
  - 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)`.
165
168
  - 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.
166
169
  - 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.
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" }`.
170
+ - Canonical slot names auto-fill from the built-in material library when the world does not declare them (name list in <texture_dsl_usage>). Imported library meshes retain private material definitions; restyle them by editing those definitions.
168
171
  - 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.
169
172
  - Hollow building window walls (where GlassTransparent would expose the empty shell): `material TowerWin { base = "InteriorRooms" shader = "interior_window_pbr" tint = "#D8CFC0" glassTint = "#3A4E4A" }` - opaque fake-parallax rooms, night-lit cells automatic. Base picks the interior per building: `"InteriorRooms"` homes, `"InteriorOffice"` workplaces. Homes get side drapes by default (`drapeSides` = per-side width 0..0.35, 0 disables; offices default 0). `tint` = mullions; `roomW/roomH/roomDepth/bayW` m override 4.41/3.3/4.0/1.47 - repeat PITCHES of the fake-room grid (set to the real floor/bay pitch), not opening sizes; `roomOffsetX/roomOffsetZ` m shift the grid datum (default 0).
170
173
  </mesh_dsl_usage>
171
174
 
172
175
  <tree_dsl_usage>
173
176
  - `#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.
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).
177
+ - 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; -1 = the default auto, follows curve_jitter_pct) + 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_normal_up_pct (0-100; bends leaf normals toward straight-up - higher = flatter, brighter, less noisy canopy shading; default 30), far_leaf_normal_up_pct (0-100; extra bend on the far billboard only - raise it when distant forests read dark and patchy; default 0), 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 leaf brightness; default 100 = matched to the near crown, <100 darkens), 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). Appearance keys (display-only; defaults = as authored): leaf_hue_deg / trunk_hue_deg (-180..180), leaf_sat_pct / trunk_sat_pct and leaf_val_pct / trunk_val_pct (0-200, 100 = as authored), leaf_val_var_pct / trunk_val_var_pct (0-100; deterministic per-tree brightness spread, the same tree near and far; defaults 10 / 0), leaf_tint / trunk_tint (#RRGGBB multiply - the only recolor that acts on white or gray art). The far billboard inherits them all; far_leaf_hue_deg / far_leaf_sat_pct / far_leaf_tint and far_trunk_hue_deg / far_trunk_sat_pct / far_trunk_shade_pct (0-150) / far_trunk_tint are extra billboard-only deltas (default = no change).
175
178
  - 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).
176
179
  - 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).
177
180
  - 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.
178
181
  - Authored trunk path (any mode): `trunk_point: x, y, z` lines (cm, up to 8, z strictly ascending) draw the trunk's xy course as a smooth spline through the knots, replacing trunk_lean_deg/trunk_sweep_pct (trunk_noise still layers on top). Above the top knot the offset holds, so put the top knot at trunk_height (or crown_base) for a fully drawn bole; pair with crown volumes + crown_follow_pct 100 for a hero leaning tree. Note: trees in a #forest keep a straight vertical collision capsule - keep heavy lean/sweep/trunk_point species out of forests or accept coarse trunk collision there.
179
- - Leaf card: `leaf: <TextureName>` body key (or naming the texture `TreeLeaf_<Name>`) replaces the auto-generated frond card; the texture needs `mapping = uv;`, `render_mode = cutout;` + `out.alpha`. `mapping = uv;` is required - cards are UV quads; the triplanar default renders them invisible (unknown or non-uv `leaf:` refs = load error). Author the card as a spray of MANY leaves via `sdf.pinnate` (thin leaflets for needle/frond conifers) or `pattern.foliage`, never one big leaf, with the stem at the bottom edge (v=0) - the card's base sits on the branch. Cluster cards consume only color+alpha; authored `out.normal`/`out.orm` matter only for `leaf_mode: frond` ribbon cards.
182
+ - Leaf card: `leaf: <TextureName>` body key (or naming the texture `TreeLeaf_<Name>`) replaces the auto-generated frond card; the texture needs `mapping = uv;`, `render_mode = cutout;` + `out.alpha` (no out.alpha = every card a solid square). `mapping = uv;` is required - cards are UV quads; the triplanar default renders them invisible (unknown or non-uv `leaf:` refs = load error). Author the card as a spray of MANY leaves via `sdf.pinnate` (thin leaflets for needle/frond conifers) or `pattern.foliage`, never one big leaf, with the stem at the bottom edge (v=0) - the card's base sits on the branch. Cluster cards consume only color+alpha; authored `out.normal`/`out.orm` matter only for `leaf_mode: frond` ribbon cards.
180
183
  - Bark: `bark: <TextureOrMaterialName>` body key per species (unknown name = load error); bare `TreeBark` (#texture or material) is the shared default for species without one (else built-in bark). Cheap per-species tint: `material PineBark { base = "TreeBark" tint = "#5a4630" }` + `bark: PineBark`.
181
184
  - Global wind: `SetWind(DirectionDeg, Strength01, Gustiness01)`; per-species response: `wind_pct`.
182
- - Authored integer keys expose editor adjuster-panel sliders (live rebake; the sim collider updates on save) - author the look-defining ones. When the look matters, iterate with the `preview_tree` tool before submitting.
185
+ - The editor's Tree adjuster panel lists every key with its engine default, grouped Structure / Trunk / Foliage / Far / Wind (live rebake; Save writes back only edited keys; the sim collider updates on save) - author only the look-defining ones. When the look matters, iterate with the `preview_tree` tool before submitting.
183
186
  Species recipes: see <example_tree_species> in <world_description_examples>.
184
187
  </tree_dsl_usage>
185
188
 
@@ -201,7 +204,8 @@ object Woods1 { model = "forest.PineWoods" position = (0, 0, 500) size = (12000,
201
204
 
202
205
  <scatter_dsl_usage>
203
206
  - `#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.
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).
207
+ - 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); z_offset_cm (signed cm added after terrain anchoring; negative sinks props into the ground, e.g. -20 hides a rock's flat base); terrain_normal_align_pct (0-100, default 100) tilts each instance onto the slope under it (100 = local up follows the terrain normal, 0 = always world-up): rocks, debris, roots want 100; anything that must stand upright (posts, signs, bushes) wants 0-30; rotation_min_deg / rotation_max_deg (-360..360, min <= max, default -180/180) bound the per-instance random yaw about that up axis: -20/20 = nearly aligned, 0/0 = no random yaw (all face the region object's forward); `shape: "<svg path data>"` draws the region outline instead of filling the whole box: path data in a 0..100 x 0..100 frame that IS the box (page-up = +Y, (0,0) = the box's -x/+y corner), nested subpaths = holes and islands, instances outside the outline are skipped (display-only; `#forest` stays box-shaped because trunks collide) - a lake shore, a park, a roadside strip as one drawing over one region object.
208
+ - The editor's Scatter adjuster panel (select the region object) lists every key with its default, filter keys as on/off switches (live re-scatter; Save writes back only edited keys) - author only the look-defining ones.
205
209
  Example:
206
210
  #scatter Boulders
207
211
  model: "mesh.Rock"
@@ -344,7 +348,7 @@ event OnTuneChanged {
344
348
  - A shared canvas still routes clicks per-player: ClickButton handlers that mutate per-player state must branch on Event.ButtonClicker.
345
349
  - 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
350
  - 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).
351
+ - Per-player build ghosts: DrawPreviewForPlayer(GetPlayerIndex(), SavedObject.X, pos, rot) each frame — only that player's client renders it (DrawPreview draws on every client). A see-through cell ghost = a saved display with `opacity = 0.35`, or a wireframe #mesh for a pure outline.
348
352
  - 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).
349
353
  - 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.
350
354
  - 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.
@@ -361,7 +365,7 @@ event OnTuneChanged {
361
365
  - Never assume a fixed player count or index order (players join/leave).
362
366
  - Determinism caveats:
363
367
  - Time is sim-time and pause-aware: TimeSecs/TimeTicks freeze on pause and may reset on world reset, so capture your own baseline (StartTime=TimeSecs()); RawTimeSecs/RawTimeTicks is unfrozen raw time.
364
- - RandomNumber/RandomPosition* are deterministic (seeded per tick + calling object + call index, so repeated or cross-object same-tick calls draw fresh values); inside player input events (key/mouse/action) all Random* calls are seeded by that input edge instead, so the predicted result matches the final one. RandomChancePercent outside input events returns false during client prediction, so there use it only for delay-tolerant side effects (e.g. drops), not moment-to-moment combat. A fresh load always starts at the same tick, so the FIRST draws repeat across sessions (identical opening deck/magazine every load) — mix a per-session value (e.g. TimeTicks() at game start) into anything that must differ between sessions.
368
+ - RandomNumber/RandomPosition* are deterministic (seeded per tick + calling object + call index, so repeated or cross-object same-tick calls draw fresh values); inside player input events (key/mouse/action) all Random* calls are seeded by that input edge instead, so the predicted result matches the final one. RandomChancePercent outside input events returns false during client prediction, so there use it only for delay-tolerant side effects (e.g. drops), not moment-to-moment combat. A fresh load always starts at the same tick, so the FIRST draws repeat across sessions (identical opening deck/magazine every load) — mix a per-session value (e.g. TimeTicks() at game start) into anything that must differ between sessions. List.Shuffle/GetRandomElement(s) and RandomChancePercent (outside input events) draw from ONE persistent stream, separate from RandomNumber's per-call derivation, so that stream repeats its sequence on every fresh load too.
365
369
  - Don't rely on cross-object event ordering (OnSpawned order across objects is unspecified).
366
370
  - Session APIs (for CUSTOM in-game browsers only - the engine ships a built-in session browser + share/visibility panel for every project; most games need no session code):
367
371
  - AdvertiseSession()/StopAdvertisingSession() toggle whether this session is listed in the project's public session list (same switch the host's engine panel sets; last write wins; play-mode entries auto-list - a game that wants private sessions calls StopAdvertisingSession()). Unlisted sessions stay joinable via share link.
@@ -385,6 +389,7 @@ event OnSpawned {
385
389
  buddy.SetRenderVisibleOnlyToPlayerIndex(me) // private world mesh/object for this player
386
390
  }
387
391
  #end
392
+ - A shared button meant to serve the party once has ONE click state: hiding or destroying it in the handler ends it for everyone - track per-player claimed status instead.
388
393
  </multiplayer_guidance>
389
394
 
390
395
  <multi_stage_projects>
@@ -524,6 +529,7 @@ event OnSpawned {
524
529
  - Bone/pose simcalls (SetBoneRotation family, SetUpperBodyAnimationWeight, SetRenderOffset) reject crowd instances - use a regular object for featured NPCs needing bone-level control.
525
530
  - ClimbingCrowdObject: like CrowdObject, but simulated by the deterministic "climbing crowd" step (lean crowd bodies)
526
531
  - Prefer it over plain WorldObject for player-chasing enemies in flat first-/third-person worlds.
532
+ - Declare with `climbingCrowd Name : Class { ... }` (scene or `saved` scope); plain `crowd` is the avoidance CrowdObject.
527
533
  - Always seeks the nearest player in XY each tick; players are never pushed (climbers always yield)
528
534
  - Default speed: 150 cm/s. Use SetDefaultClimbingCrowdSpeed(Speed) to change the default for newly spawned climbers; use Object.SetClimbingCrowdSpeed(Speed) to override an individual climber. Do not use SetPhysicsSpeed.
529
535
  - Terrain + static primitive obstacles; supports elevator-like climbing
@@ -531,7 +537,7 @@ event OnSpawned {
531
537
  - DisplayObject: like WorldObject but visual-only — no collision, no interaction events (Touched, ClickThisObject, etc. will never fire), doesn’t respond to forces
532
538
  - When a display shell forms a player-reachable structure (building, wall), add a static collision core or invisible collider proxy behind it — display-only geometry is walked through.
533
539
  - EffectObject: visual effects
534
- - pointLight: local point light. `pointLight L { position = (0, 0, 150) radius = 500 intensity = 4 color = (1, 0.85, 0.6) }` - radius cm (default 100 = dim 1m pool); optional cast_shadow = true, light_diffuse_scale/light_specular_scale. For a bulb on a mesh prop, prefer an emitter(...) socket in the #mesh (see <mesh_dsl_usage>)
540
+ - pointLight: local point light. `pointLight L { position = (0, 0, 150) radius = 500 intensity = 4 color = (1, 0.85, 0.6) }` - radius cm (default 100 = dim 1m pool); falloff is intensity * (1 - d/radius)^2 / (d^2 + 1) with d in metres, against a default sun of 4: intensity 5 lights a surface at 1 m to ~40% of sunlit white, at 2 m ~10%, at 4 m ~1% - a lamp that must read lit at 2 m wants ~40-50, a room-filling wash at 5 m ~300+ with a matching radius; a flat white blob means intensity, not radius, is too high; optional cast_shadow = true, light_diffuse_scale/light_specular_scale. For a bulb on a mesh prop, prefer an emitter(...) socket in the #mesh (see <mesh_dsl_usage>)
535
541
  - spotLight: cone along the object's +X facing (aim with rotation); pointLight keys plus cos_inner/cos_outer = cosine of the half-angle, not degrees (defaults 24deg/30deg)
536
542
  - diffuseOnlyLight: diffuse-only, flat core with fade_width (cm) fade shell; pointLight keys minus cast_shadow/light_specular_scale
537
543
  - areaLight: area light; pointLight keys minus cast_shadow
@@ -543,7 +549,7 @@ event OnSpawned {
543
549
  <variable_types>
544
550
  - Number — real (integers/decimals/scientific); arithmetic includes % and %= (modulus).
545
551
  - Bool — true/false.
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").
552
+ - 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.Substring(Start, Count), Text.Length(), Text.ToNumber() (0 if not numeric). .ToText() renders minimal form ("1", not "1.000").
547
553
  - Object — handle to a world/UI object; may be None
548
554
  - Timer — countdown handle; new timers start in Done state.
549
555
  - Vector — (X,Y,Z) direction/offset; also used for RGB triples.
@@ -552,7 +558,7 @@ event OnSpawned {
552
558
  - 'var X: List<T>' needs no initializer (class scope or local); lists start empty.
553
559
  - Bare 'var X: List' infers T from usage; annotate List<T> explicitly when inference reports conflicting evidence.
554
560
  - No List<List<T>>. Non-empty literals ([1,2,3]) are const-only: runtime lists start empty then Add, or Resize(n, default).
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.
561
+ - '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. Other classes read a const through a typed cast, (Scene.DbObj as Db).Names.Get(i); the value is inlined, so the receiver must be a plain handle (Scene.X, a param, a local), not a call.
556
562
  - 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
563
  - 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).
558
564
  - Contains/IndexOf: List<Number|Bool|Vector|Position> and object lists only (not List<Text>/List<Timer>).
@@ -569,7 +575,7 @@ event OnSpawned {
569
575
  - Fields: Number | Bool | Vector | Position | Object (plain or class-typed, e.g. 'var M: Monster' — member calls through class-typed fields are validated statically). Max 16 slots; Vector/Position count as 3, Object counts as 1. No Text/Timer/nested-struct fields yet. Object fields default to None (zeroed class-scope structs and Resize-grown list elements included).
570
576
  - Construct positionally with one arg per field: 'var S = Stat(100, Vector(1,0,0))'. Read/write fields ('S.Health += 1', 'S.Speed.X = 2') on locals, Self vars, and list elements ('Table.Get(i).Health = 5' writes back); copies are by value ('var C = S' snapshots), so fields on a function's returned struct need a var first.
571
577
  - 'var Stats: Stat' without an initializer starts zeroed in any scope, Object fields None (no struct literals at class scope; fill in OnSpawned). Structs work as function params/returns.
572
- - List<struct> is supported ('var Table: List<Stat>') with Add/Get/Set/RemoveAt/Swap/Shuffle/Resize(count)/Count/IsEmpty/Clear + for-each; not Contains/IndexOf/Pop/Last/GetRandomElement or Resize with fill.
578
+ - List<struct> is supported ('var Table: List<Stat>') with Add/Get/Set/RemoveAt/Swap/Shuffle/Resize(count)/Count/IsEmpty/Clear + for-each; IndexOfField("Field", V)/ContainsField (Number/Bool/Object fields); not Contains/IndexOf/Pop/Last/GetRandomElement or Resize with fill.
573
579
  - Lists cap at ~1M elements: an every-frame Add with no Clear/dedupe (e.g. selections, hit logs) reaches the cap in minutes of play and stops the world with CapacityExceeded - clear or bound accumulating lists.
574
580
  - No struct operators ('==' included) — compare fields; no scene-clause overrides.
575
581
 
@@ -611,6 +617,7 @@ object test : MeleeEnemy { size = (70,70,100), override MaxHP = 100 }
611
617
  - Basic cube is 100x100x100 cm, sphere 100 cm diameter
612
618
  - Basic models: cube, sphere, cylinder, capsule (long axis +Z), cone (apex +Z), wedge (slope rises toward +X)
613
619
  - Object position is the BOTTOM center: the object rests its base at position.z (top = position.z + size.z). A ground slab with its top at z=0 is position = (0, 0, -H) size = (X, Y, H).
620
+ - Two same-facing faces on one plane shimmer (z-fight): stacked slabs sharing a top, a plane at floor height, a sign flush in a wall, a prop placed twice. Keep >=1cm between them or give the overlay's material render_mode = decal; validation lists such pairs.
614
621
  - Object size is clamped per axis: 50,000 cm for solid objects, 100,000 cm display-only (10,000 cm dynamic). Meshes scale to object size, so oversized objects render SQUASHED — tile large ground/water/road geometry into pieces under the cap.
615
622
  - cone/wedge collide as boxes. Walkable ramp = rotated thin cube, e.g. size = (630, 300, 20) rotation = (0, -18, 0) rises toward +X
616
623
  - 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.
@@ -747,7 +754,7 @@ Applies to all physics types: Dynamic, Static, and Kinematic.
747
754
  - 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
755
  - 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).
749
756
  - 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.
757
+ - Seats (players riding vehicles/mounts): Car.SeatPlayer(p, "driver") parks a player (user or bot) on that model anchor - a model car without one mints it in its own class: SetModelAnchor(GetModel(), "driver", <seat point = the pan surface the rider sits on, model-local cm: cabin center, driver side, ~50cm above the underbody>, Vector(0,0,0)), once per model; an optional "wheel" anchor at the rim center puts the driver's hands on it; Car.SeatPlayerAt(p, "driver", Vector(0,0,60)) is for primitive-built seats only. 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.
751
758
  - 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.
752
759
  - 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.
753
760
  - 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.
@@ -761,7 +768,7 @@ Applies to all physics types: Dynamic, Static, and Kinematic.
761
768
  </attachments>
762
769
 
763
770
  <easing>
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.
771
+ - 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[, Delay]]) etc., see <ui>) instead of a loop.
765
772
  - The tween loop (works for any value; write the exact target at the end):
766
773
  var T = 0
767
774
  var Start = O.GetPosition()
@@ -795,7 +802,7 @@ Movement Laws (single-frame vs over-time)
795
802
  - Arrival at an OBJECT: colliders stop the mover before centers meet, so DistanceTo(target) may never drop below both half-extents (reads as a freeze while Move* keeps firing). Gate on SurfaceGapTo(Target) <= margin instead.
796
803
  - Timing & readback: see <frame_and_scheduling_model> for tick order and GetPosition/GetRotation semantics.
797
804
 
798
- - Clarification: SnapToGround raycasts downward (-Z) from the object's bottom-center and snaps to the first WorldObject hit (ignores PhysicsObject/PlayerObject/CrowdObject etc.). It overrides any Z offset you set earlier in the same frame. For stacking on tiles/objects, compute Z = baseZ + baseSizeZ/2 + itemSizeZ/2 instead of using SnapToGround. Flat/plane items (rugs, road markings, decals): add ~1cm more — face-on-face at the same height shimmers (z-fights), worse at long view distances.
805
+ - Clarification: SnapToGround raycasts downward (-Z) from the object's bottom-center and snaps to the first WorldObject hit (ignores PhysicsObject/PlayerObject/CrowdObject etc.). It overrides any Z offset you set earlier in the same frame. For stacking on tiles/objects, compute Z = baseZ + baseSizeZ/2 + itemSizeZ/2 instead of using SnapToGround. Flat/plane items (rugs, road markings, decals): add ~1cm more — face-on-face at the same height shimmers (z-fights).
799
806
  </movement_laws>
800
807
 
801
808
  <inputs>
@@ -843,7 +850,8 @@ Movement Laws (single-frame vs over-time)
843
850
  - Digits: Key.Num0 .. Key.Num9
844
851
  - Mouse: Key.LeftMouseButton, Key.RightMouseButton, Key.MiddleMouseButton
845
852
  - Navigation Keys: Key.UpArrow, Key.DownArrow, Key.LeftArrow, Key.RightArrow
846
- - Control Keys: Key.Tab, Key.Space, Key.Enter, Key.Backspace, Key.LeftShift, Key.RightShift, Key.LeftControl, Key.RightControl, Key.LeftAlt, Key.RightAlt
853
+ - Control Keys: Key.Tab, Key.Space, Key.Enter, Key.Backspace, Key.Escape, Key.LeftShift, Key.RightShift, Key.LeftControl, Key.RightControl, Key.LeftAlt, Key.RightAlt
854
+ - Function Keys: Key.F1 .. Key.F12
847
855
  - Symbol Keys: Key.Tilde, Key.Minus, Key.Equals, Key.LeftBracket, Key.RightBracket, Key.Backslash, Key.Semicolon, Key.Quote, Key.Comma, Key.Period, Key.Slash
848
856
  </key_codes>
849
857
 
@@ -930,15 +938,16 @@ PrintTag(Tag: Number)
930
938
  PrintDivider()
931
939
  SetAutoGameLogOn()
932
940
  PlayerPrint(PlayerIndex: Number, Text: Text)
933
- SetSunIntensity(Intensity: Number) // Default: 4 [global: all clients]
934
- SetSunSpecularScale(Scale: Number) // Default: 1
941
+ SetSunIntensity(Intensity: Number) // Default: 4. Sunlit white ~ Intensity/pi pre-tonemap; above ~8 the tonemap shoulder flattens gains (12 and 18 look alike) - lift a dark scene with SetExposure, fill shadows with SetSkyLightIntensity [global: all clients]
942
+ SetSunSpecularScale(Scale: Number) // Default: 1 [global: all clients]
935
943
  SetSunRotation(Rotation: Number) // Default: 10. Azimuth, degrees from +X toward +Y; shadows fall toward Rotation+180 [global: all clients]
936
944
  SetSunTilt(Tilt: Number) // Default: 46. Elevation above the horizon, degrees (90 = overhead) [global: all clients]
945
+ SetSnowCover(Amount: Number) // Default: 0. Snow cover 0..1 on props and characters: sky-exposed upward faces blend to snow and take snow lighting; terrain keeps its own layers; emissive, glass and snow_cover = "off" materials stay bare [global: all clients]
937
946
  SetSkybox(Skybox: Object)
938
- SetSkyboxHorizonTilt(AngleDegrees: Number) // Default: 0
939
- SetSkyboxRotation(RotationDegrees: Number) // Default: 0
947
+ SetSkyboxHorizonTilt(AngleDegrees: Number) // Default: 0 [global: all clients]
948
+ SetSkyboxRotation(RotationDegrees: Number) // Default: 0 [global: all clients]
940
949
  SetSkyboxIsNight(IsNight: Bool) // true swaps to the FIXED near-black night skybox - it ignores tint/haze, so it will not blend with fog (hard dark zenith). Night sky too black / seams against fog -> keep this false and build twilight: SetSunTilt near 0, dark SetSkyboxTint + SetSkyboxHaze, stars via SetSkyboxStarIntensity
941
- SetSkyboxSunVisible(Visible: Bool)
950
+ SetSkyboxSunVisible(Visible: Bool) [global: all clients]
942
951
  SetSkyboxTint(Color: Vector) [global: all clients]
943
952
  SetSkyboxSaturation(Saturation: Number) // Default: 1. With procedural sky, sky grade calls also re-tint the ambient lighting (IBL) [global: all clients]
944
953
  SetSkyboxExposure(ExposureStops: Number) // Default: 0 [global: all clients]
@@ -946,32 +955,32 @@ SetSkyLightIntensity(Intensity: Number) // Default: 2 [global: all clients]
946
955
  SetSkyLightColor(Color: Vector) [global: all clients]
947
956
  SetSkyLightSaturation(Saturation: Number) // Default: 1 [global: all clients]
948
957
  SetBloomThreshold(Threshold: Number) // Default: 2 [global: all clients]
949
- SetBloomSoftKnee(SoftKnee: Number) // Default: 0.5
958
+ SetBloomSoftKnee(SoftKnee: Number) // Default: 0.5 [global: all clients]
950
959
  SetBloomIntensity(Intensity: Number) // Default: 0.15 [global: all clients]
951
- SetBloomClampValue(Clamp: Number) // Default: 1
960
+ SetBloomClampValue(Clamp: Number) // Default: 1 [global: all clients]
952
961
  SetSsaoIntensity(Intensity01: Number) // Default: 1 [global: all clients]
953
962
  SetSsaoDistance(DistanceMeters: Number) // Default: 0.5 [global: all clients]
954
963
  SetSsrLow()
955
964
  SetSsrHigh()
956
- SetSsrOff()
957
- SetSsrRoughnessBlurMultiplier(Multiplier: Number) // Default: 2
958
- SetSsrRoughnessMipClamp(LowerMip: Number, UpperMip: Number) // Default: 0, 0
959
- SetSsrIntensity(Intensity0To10: Number) // Default: 1
965
+ SetSsrOff() [global: all clients]
966
+ SetSsrRoughnessBlurMultiplier(Multiplier: Number) // Default: 2 [global: all clients]
967
+ SetSsrRoughnessMipClamp(LowerMip: Number, UpperMip: Number) // Default: 0, 0 [global: all clients]
968
+ SetSsrIntensity(Intensity0To10: Number) // Default: 1 [global: all clients]
960
969
  SetShadowBlurRadius(Radius: Number) // Default: 2.8 [global: all clients]
961
- SetLocalShadowBlurRadius(Radius: Number) // Default: 12
962
- SetShadowDepthBiasConstant(Units: Number) // Default: 1
963
- SetShadowDepthBiasSlopeScale(Scale: Number) // Default: 2.5
970
+ SetLocalShadowBlurRadius(Radius: Number) // Default: 12 [global: all clients]
971
+ SetShadowDepthBiasConstant(Units: Number) // Default: 1 [global: all clients]
972
+ SetShadowDepthBiasSlopeScale(Scale: Number) // Default: 2.5 [global: all clients]
964
973
  SetShadowOpacity(Opacity01: Number) // Default: 1 [global: all clients]
965
- SetStaticLodTransitions(Lod1Size: Number, Lod2Size: Number, Lod3Size: Number) // Default: 0.18, 0.07, 0.025
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
974
+ SetStaticLodTransitions(Lod1Size: Number, Lod2Size: Number, Lod3Size: Number) // Default: 0.18, 0.07, 0.025 [global: all clients]
975
+ 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 [global: all clients]
967
976
  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]
968
977
  SetSaturation(Saturation: Number) // Saturation multiplier. Default: 1 = neutral; 0 = grayscale, 2 = vivid [global: all clients]
969
978
  SetExposure(Exposure: Number) // EV stops, pre-tonemap: +1 = twice as bright, -1 = half. Default: 0 [global: all clients]
970
979
  SetShadowLift(Amount: Number) // Default: 2; 0 = off [global: all clients]
971
980
  SetShadowLiftThreshold(Threshold: Number) // Default: 0.06 [global: all clients]
972
- SetSmaaEdgesThreshold(Threshold01: Number) // Default: 0.01
973
- SetAdaptiveRenderQualityEnabled(Enabled: Bool) // Default: true. Renderer may temporarily drop SSR/SSAO/bloom on slow frames; false pins full quality
974
- SetGlobalMipBias(Bias: Number) // Default: 0. Mobile wasm applies its startup bias only; changing this later may not rebuild already-uploaded textures.
981
+ SetSmaaEdgesThreshold(Threshold01: Number) // Default: 0.01 [global: all clients]
982
+ SetAdaptiveRenderQualityEnabled(Enabled: Bool) // Default: true. Renderer may temporarily drop SSR/SSAO/bloom on slow frames; false pins full quality [global: all clients]
983
+ SetGlobalMipBias(Bias: Number) // Default: 0. Mobile wasm applies its startup bias only; changing this later may not rebuild already-uploaded textures. [global: all clients]
975
984
  SetVignetteStrength(Strength: Number) // Default: 0 [global: all clients]
976
985
  SetVignetteRadius(Radius: Number) // Default: 0.75 [global: all clients]
977
986
  SetVignetteSoftness(Softness: Number) // Default: 0.25 [global: all clients]
@@ -979,8 +988,8 @@ SetVignetteColor(Color: Vector) [global: all clients]
979
988
  SetDefaultClimbingCrowdSpeed(Speed: Number)
980
989
  SetClimbingCrowdPaused(IsPaused: Bool) // Freeze climbingCrowd step (movement + depenetration/climb).
981
990
  SetClimbingCrowdSolveSteps(Steps: Number)
982
- SetAllLocalShadowsEnabled(Enabled: Bool)
983
- SetAllDecalsEnabled(Enabled: Bool)
991
+ SetAllLocalShadowsEnabled(Enabled: Bool) [global: all clients]
992
+ SetAllDecalsEnabled(Enabled: Bool) [global: all clients]
984
993
  SetGravity(Gravity: Number)
985
994
  SetTerminalVelocityZ(MaxFallSpeed: Number)
986
995
  SetFogStartDistance(StartDistance: Number) // Default: 10. Meters (not cm). The skybox is fogged only via SetFogSkyboxAffect or height fog; underwater, automatic murk overrides all fog settings. One world value - per-player override: SetFogDistances [global: all clients]
@@ -989,10 +998,10 @@ SetHeightFogEnabled(Enabled: Bool) [global: all clients]
989
998
  SetHeightFogTop(Top: Number) // Default: 50. Meters [global: all clients]
990
999
  SetHeightFogBottom(Bottom: Number) // Default: 0. Meters [global: all clients]
991
1000
  SetHeightFogSoftness(Softness: Number) // Default: 1 [global: all clients]
992
- SetGpuCullingDistance(DistanceMeters: Number) // Default: 0 (distance culling off)
993
- SetFrustumCulling(Enabled: Bool)
994
- SetFrustumCullingMargin(MarginMeters: Number) // Default: 0
995
- SetDecalCullingDistanceM(DistanceMeters: Number) // Default: 0
1001
+ SetGpuCullingDistance(DistanceMeters: Number) // Default: 0 (distance culling off) [global: all clients]
1002
+ SetFrustumCulling(Enabled: Bool) [global: all clients]
1003
+ SetFrustumCullingMargin(MarginMeters: Number) // Default: 0 [global: all clients]
1004
+ SetDecalCullingDistanceM(DistanceMeters: Number) // Default: 0 [global: all clients]
996
1005
  SetGrassSpacingCm(SpacingCm: Number) // Default: 0 (auto) [global: all clients]
997
1006
  SetGrassTerrainActiveDistanceM(DistanceMeters: Number) // Default: 140 [global: all clients]
998
1007
  SetGrassCullDistanceM(DistanceMeters: Number) // Default: 140 [global: all clients]
@@ -1008,7 +1017,7 @@ SetAssetSubmeshSaturation(ModelAsset: Object, SubmeshIndex: Number, Saturation:
1008
1017
  SetAssetSubmeshBrightness(ModelAsset: Object, SubmeshIndex: Number, Brightness: Number) // Gain: 1 = neutral
1009
1018
  SetAssetSubmeshContrast(ModelAsset: Object, SubmeshIndex: Number, Contrast: Number) // Multiplier around mid-gray: 1 = neutral, 0 = flat gray. Use only when the user asks for it specifically; Tint/Brightness/Saturation cover normal fitting
1010
1019
  SetAssetSubmeshTint(ModelAsset: Object, SubmeshIndex: Number, TintRGB: Vector)
1011
- SetAssetSubmeshEmissionIntensity(ModelAsset: Object, SubmeshIndex: Number, Intensity: Number) // Linear glow multiplier for this submesh; composes with object emission
1020
+ SetAssetSubmeshEmissionIntensity(ModelAsset: Object, SubmeshIndex: Number, Intensity: Number) // Linear glow for this submesh, same ladder as SetEmissionIntensity; composes with object emission
1012
1021
  SetAssetSubmeshMinRoughness(ModelAsset: Object, SubmeshIndex: Number, MinRoughness: Number)
1013
1022
  SetAssetSubmeshRoughnessSmoothStepRemap(ModelAsset: Object, SubmeshIndex: Number, Enabled: Bool)
1014
1023
  SetAssetSubmeshNormalStrength(ModelAsset: Object, SubmeshIndex: Number, Strength: Number)
@@ -1023,7 +1032,7 @@ ClearAssetSubmeshMaterial(ModelAsset: Object, SubmeshIndex: Number) // Clears ex
1023
1032
  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
1033
  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
1025
1034
  SetMaterialParameterNumber(MaterialAsset: Object, Slot: Number, Value: Number)
1026
- SetMaterialParameterTexture(MaterialAsset: Object, Slot: Number, ImageAsset: Object)
1035
+ SetMaterialParameterTexture(MaterialAsset: Object, Slot: Number, ImageAsset: Object) // Bind an Image asset into the material's texN WGSL slot (Slot 0..16; None clears). A model's own imported maps bind via Ori.modeltex.<creator>.<name>.<color|normal|orm> imports
1027
1036
  GetMaterialParameterNumber(MaterialAsset: Object, Slot: Number) -> Number
1028
1037
  GetMaterialParameterTexture(MaterialAsset: Object, Slot: Number) -> Object
1029
1038
  SetAnimationAssetSpeedFactor(AnimationAsset: Object, SpeedFactor: Number)
@@ -1084,8 +1093,8 @@ Position.ShiftLeft(Distance: Number, LeftOfObject: Object) -> Position
1084
1093
  Position.ShiftRight(Distance: Number, RightOfObject: Object) -> Position
1085
1094
  Position.ShiftForward(Distance: Number, ForwardOfObject: Object) -> Position
1086
1095
  Position.ShiftBackward(Distance: Number, BackwardOfObject: Object) -> Position
1087
- Position.SnapToTile2D(GridSize: Number, Origin: Position) -> Position
1088
- Position.SnapToVoxel(GridSize: Number, Origin: Position) -> Position
1096
+ Position.SnapToTile2D(GridSize: Number, Origin: Position) -> Position // Center of the GridSize tile containing this point in XY (Z unchanged)
1097
+ Position.SnapToVoxel(GridSize: Number, Origin: Position) -> Position // Center of the GridSize cell containing this point (cells span [k*GridSize, (k+1)*GridSize) per axis from Origin) - the cell key VoxelGet/VoxelSet read; snap `hit - Direction.GetNormal()` for the cell to place on
1089
1098
  Position.HasObjectAtPoint() -> Bool
1090
1099
  Position.HasObjectAtPointWithTag(Tag: Number) -> Bool
1091
1100
  GetObjectByIndex(Index: Number) -> Object
@@ -1396,8 +1405,8 @@ SpawnAtTile(SavedObject: Object, TileXYZ: Vector) -> Object
1396
1405
  Object.TeleportToTile(TileX: Number, TileY: Number)
1397
1406
  Object.TeleportToTile(TileX: Number, TileY: Number, TileZ: Number)
1398
1407
  Object.TeleportToTile(TileXYZ: Vector)
1399
- Object.SetRotation(Rotation: Vector) // 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
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
1408
+ Object.SetRotation(Rotation: Vector) // Euler degrees X = roll, Y = pitch, Z = yaw, applied X then Y then Z about WORLD axes (R = Rz*Ry*Rx, same as Vector.RotateByEuler(Z, Y, X)); a facing/turn goes in Z. 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
1409
+ Object.SetRotation(X: Number, Y: Number, Z: Number) // Euler degrees X = roll, Y = pitch, Z = yaw, applied X then Y then Z about WORLD axes (R = Rz*Ry*Rx, same as Vector.RotateByEuler(Z, Y, X)); a facing/turn goes in Z. 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
1401
1410
  Object.SetGroupRotationWithPivot(Pivot: AnyBasicType, Rotation: Vector)
1402
1411
  Object.LookAtGroupWithPivot(Pivot: AnyBasicType, Target: AnyBasicType)
1403
1412
  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
@@ -1430,7 +1439,7 @@ Object.SetWaterColorMixRange(ShallowCm: Number, DeepCm: Number) // Depth range f
1430
1439
  Object.SetWaterTransparency(Clarity: Number) // 1 = clear (full refraction), 0 = opaque surface.
1431
1440
  Object.SetEmissionColor(RGB: Vector) // Glow tint; unset/near-black = glows in the object's own color
1432
1441
  Object.SetEmissionColor(R: Number, G: Number, B: Number) // Glow tint; unset/near-black = glows in the object's own color
1433
- Object.SetEmissionIntensity(Intensity: Number) // Linear glow: 0 off, 1 soft self-lit, 4 lamp, 16+ neon bloom
1442
+ Object.SetEmissionIntensity(Intensity: Number) // Linear glow, 0 = off: 0.3-0.5 = tinted glow that keeps its colour, 1 = as bright as a sunlit white surface, 2 = pastel, 4+ reads white (bloom carries the tint), 8+ only for sub-pixel LEDs / sun discs
1434
1443
  Object.SetLightIntensity(Intensity: Number)
1435
1444
  Object.SetLightFadeWidth(FadeWidthCm: Number) // diffuseOnlyLight only (edge fade band; default 30, clamped to the light radius)
1436
1445
  Object.SetLightCastShadow(Enabled: Bool)
@@ -1487,8 +1496,12 @@ Object.SetLocalPlacementGhostMode(Mode: Number) // PlayerObject only. 0 = single
1487
1496
  Object.DisableLocalPlacementGhost() // PlayerObject only. Hides the local build ghost
1488
1497
  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
1498
  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)
1499
+ FieldStamp(FieldName: Text, Position: Position, RadiusCm: Number, Strength: Number) // Press a mark into a #fieldmap canvas this frame (display-only; see <field_maps>). Negative Strength erases
1500
+ FieldStampBoot(FieldName: Text, Position: Position, Facing: Vector, LengthCm: Number, Strength: Number, Side: Number) // Oriented boot print (heel, toe, sole tread) pressed into a #fieldmap: Facing = heading vector, LengthCm ~28, Side alternates -1/1 per step; needs <= 2.5 cm per texel for the tread to read (resolution 1024 with window_m <= 24). Round stamp: FieldStamp
1501
+ FieldStampCapsule(FieldName: Text, From: Position, To: Position, RadiusCm: Number, Strength: Number) // Flat-floored segment press for continuous furrows: From = last frame's position, To = now; one call per moving frame, no disc scallops. Rim as FieldStamp
1502
+ SetFieldMaterial(FieldName: Text, Material: Object) // Custom #fieldmap kernel: Material whose #shader defines ori_field_update (see <field_maps>). None restores the default stamp/heal kernel
1503
+ 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. The rider auto-plays .sit, seated ON the anchor point (the sit clip lifts the hips a pelvis height above it). Model vehicles without a driver socket: SetModelAnchor(GetModel(), "driver", <seat point = the pan surface, model-root local cm: cabin center, driver side, ~50cm above the underbody>, Vector(0,0,0)) once per model (all instances share it), then SeatPlayer; a "wheel" anchor at the rim center puts the driver's hands on it
1504
+ Object.SeatPlayerAt(Target: Object, SeatName: Text, Offset: Vector) -> Bool // SeatPlayer for objects without model anchors. SeatName is the seat key SeatOccupant queries; Offset (cm, local axes, rotates with it) is from this object's TOP-FACE CENTER - Vector(0,0,0) seats the rider on top (glutes on the surface), facing this object's +X with knees ~46cm forward. For PRIMITIVE-BUILT seats only (a cushion block). Never on a model vehicle - its top face is the roof; seat models with SetModelAnchor + SeatPlayer
1492
1505
  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
1506
  Object.SeatOccupant(SeatName: Text) -> Object // Player seated on this object's named seat, or a None-like Object (check Exists())
1494
1507
  Object.SeatedIn() -> Object // Object this player is seated on, or a None-like Object (check Exists())
@@ -1496,8 +1509,8 @@ TileGridRefreshFromTerrainRect(Grid: List, X1: Number, Y1: Number, X2: Number, Y
1496
1509
  TileGridDebugOverlay(Grid: List, Enabled: Bool)
1497
1510
  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
1498
1511
  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
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
1500
- SetVolumetricLightDensity(Percent: Number) // Haze thickness the shafts scatter in, percent of baseline. Default: 100; lower = crisper thinner shafts, higher = foggier
1512
+ 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 [global: all clients]
1513
+ SetVolumetricLightDensity(Percent: Number) // Haze thickness the shafts scatter in, percent of baseline. Default: 100; lower = crisper thinner shafts, higher = foggier [global: all clients]
1501
1514
  Object.EnableLocalWeaponView(Offset: Position, RotationDegrees: Vector, Scale: Number, FovDegrees: Number) // EnableLocalWeaponView + own weapon FOV: FovDegrees 0 = follow camera; ~50-70 = classic FPS viewmodel that ignores ADS zoom and never clips walls
1502
1515
  SetSoundVolume(Sound: Object, Volume: Number) // Live per-asset volume (1 = as authored): retunes every instance already playing, loops included, and every later play; the per-play PlaySound volume multiplies on top. 0 mutes but keeps loops running - StopSound to end them
1503
1516
  FadeOutSound(Sound: Object, Seconds: Number) // Ramps this sound's playing instances to silence over Seconds, then stops them; queued plays drop immediately, plays started after the call are unaffected. Music/ambience handoffs - StopSound cuts instantly
@@ -1533,13 +1546,13 @@ GetGravity() -> Number
1533
1546
  PersistGetNumber(Player: Object, Key: Text) -> Number
1534
1547
  PersistSetNumber(Player: Object, Key: Text, Value: Number)
1535
1548
  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
1549
+ 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. Every object running the call emits: with mock players each player instance emits its own sample and the expect reads the last one, so key per-player metrics by player index (Key1)
1537
1550
  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).
1538
1551
  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.
1539
1552
  AnyBasicType.NearestPlayerNonSpectator() -> Object
1540
- SetSsrRoughnessFade(StartRoughness: Number, EndRoughness: Number) // Default: 0.68, 0.7
1541
- SetSsrRayFade(NearFadePower: Number, FarFadePower: Number) // Default: 0.08, 0.8
1542
- SetSkyboxHue(HueDegrees: Number) // Default: 0
1553
+ SetSsrRoughnessFade(StartRoughness: Number, EndRoughness: Number) // Default: 0.68, 0.7 [global: all clients]
1554
+ SetSsrRayFade(NearFadePower: Number, FarFadePower: Number) // Default: 0.08, 0.8 [global: all clients]
1555
+ SetSkyboxHue(HueDegrees: Number) // Default: 0 [global: all clients]
1543
1556
  SetSkyboxContrast(Contrast: Number) // Contrast multiplier. Default: 1 = neutral [global: all clients]
1544
1557
  Hash2(X: Number, Y: Number, Seed: Number) -> Number // Deterministic [0,1): constant within each integer X/Y cell (see <procedural_generation>)
1545
1558
  ValueNoise2(X: Number, Y: Number, Seed: Number) -> Number // Deterministic smooth noise [0,1), X/Y in cell units
@@ -1598,11 +1611,11 @@ Object.SetEmitting(IsOn: Bool) // EffectObject only. false: stop spawning new pa
1598
1611
  Object.DestroyAfterParticles() // EffectObject only. Stops emission, keeps emitter following until existing particles finish, then destroys it. Use instead of Destroy() for attached looping VFX (e.g. thrusters).
1599
1612
  Object.DestroyImmediate() // EffectObject only. Destroys the emitter and hard-clears its live particles instantly (no dying tail).
1600
1613
  Object.ShowSelectionRing(Player: Object, Radius: Number, Color: Vector, ZOffset: Number) // As 3-arg; ZOffset cm shifts ring vertically from feet (negative grounds a flying unit's ring)
1601
- SetSkyboxCloudCoverage(Coverage: Number) // Procedural sky only (no SetSkybox asset). Default: 0.1; 0 = clear sky, 1 = overcast [global: all clients]
1614
+ SetSkyboxCloudCoverage(Coverage: Number) // Procedural sky only (no SetSkybox asset). Default: 0.15; 0 = clear sky, 1 = overcast [global: all clients]
1602
1615
  SetSkyboxHaze(Haze: Number) // Procedural sky only. Default: 0.5; 0.3 = crisp alpine air, 2-3 = hazy horizon [global: all clients]
1603
1616
  SetSkyboxStarIntensity(Intensity: Number) // Night star overlay brightness; visible only with the sun near/below the horizon or SetSkyboxIsNight(true). Default: 1; 0 = no stars [global: all clients]
1604
- SetSkyboxCloudDetail(Detail: Number) // Procedural sky only. Cloud fractal detail. Default: 1; 0 = soft blobs, 2 = max wispy detail
1605
- SetSkyboxCloudScale(Scale: Number) // Procedural sky only. Cloud pattern size. Default: 1; 0.5 = big clouds, 2 = many small clouds
1617
+ SetSkyboxCloudDetail(Detail: Number) // Procedural sky only. Cloud fractal detail. Default: 1; 0 = soft blobs, 2 = max wispy detail [global: all clients]
1618
+ SetSkyboxCloudScale(Scale: Number) // Procedural sky only. Cloud pattern size. Default: 1; 0.5 = big clouds, 2 = many small clouds [global: all clients]
1606
1619
  SetSkyboxGroundColor(Color: Vector) // Procedural sky only. Ground-bounce light color lighting surfaces from below. Default: (0.34, 0.44, 0.32) dim green; brightness follows the sky [global: all clients]
1607
1620
  SetSkyBillboardMaterial(Slot: Number, Material: Object) // Textured quad composited into the sky (moon/galaxy/gate). Slot 0..3. Samples the material's texture0 (e.g. a #texture color map); None clears. Show with SetSkyBillboardSize
1608
1621
  SetSkyBillboardDirection(Slot: Number, RotationDegrees: Number, TiltDegrees: Number) // Where in the sky (same convention as SetSunRotation/SetSunTilt). Default: 0, 45
@@ -1611,7 +1624,7 @@ SetSkyBillboardColor(Slot: Number, Color: Vector) // Tint. Default: (1, 1, 1)
1611
1624
  SetSkyBillboardIntensity(Slot: Number, Intensity: Number) // Default: 1; >1 glows (HDR bloom)
1612
1625
  SetSkyBillboardRoll(Slot: Number, RollDegrees: Number) // Rotation around the view direction. Default: 0
1613
1626
  SetSkyBillboardAdditive(Slot: Number, IsAdditive: Bool) // true = additive glow blend (galaxy/gate), false = alpha over sky (moon). Default: false
1614
- Object.SetTerrainSplat(SplatRules: Text) // Terrain only; SplatRules: #terrain_splat block name; baked RGB weights replace slope/height auto-blend for the SetTerrainLayers materials (R=Steep slot, G=High, B=Low; base = leftover); rules may also assign out.grass = grass density, or out.albedo = final color (colormap mode; replaces layer blending entirely). "" clears
1627
+ Object.SetTerrainSplat(SplatRules: Text) // Terrain only; SplatRules: #terrain_splat block name; baked RGB weights replace slope/height auto-blend for the SetTerrainLayers materials (R=Steep slot, G=High, B=Low; base = leftover); rules may also assign out.grass = grass density, or out.albedo = final color (colormap mode; replaces layer blending entirely). "" clears. Grid (DATA/heights) terrains only: a SpawnTerrainRecipe terrain has no baked splat texels, so the call is a silent no-op there - paint recipe ground with SetTerrainSurfaceWeightsMaterial, or sample the block into a grid (TerrainHeightsFromTerrain) first
1615
1628
  SetWaterPlanarReflection(Strength: Number, Distortion: Number) // Mirror reflection of the scene on the GLOBAL water surface (SetWaterEnabled) only; placed water meshes always use skybox reflections. Strength 0 disables, Distortion scales wave wobble. Defaults: 0.7, 0.3 [global: all clients]
1616
1629
  Object.SetHoverHPBarMaterial(Material: Object) // Custom look for caller's hover HP bar; Material.X (#shader or #texture auto-material), None reverts to default. #shader gets ctx.fraction/ctx.background_color and draws the whole bar as one quad (textures don't squash). Persists across SetHoverHPBar updates; ClearHoverBars removes it
1617
1630
  Object.SetHoverSPBarMaterial(Material: Object) // Same as SetHoverHPBarMaterial for the hover SP bar
@@ -1665,7 +1678,8 @@ SetGrassSizeVariationPercent(Percent: Number) // Global per-tuft/per-clump heigh
1665
1678
  SetGrassHeightMaxCm(HeightCm: Number) // Global grass height on wave crests, cm (default 90, clamped 0..300). Brightness ripple + ground mottle auto-follow the Min..Max swing [global: all clients]
1666
1679
  SetGrassWindSpeedPercent(Percent: Number) // Global grass wind animation speed (gust drift + sway phase) in percent (100 = default, clamped 0..500). Adjusting it never pops: phase accumulates [global: all clients]
1667
1680
  SetGrassBendStrengthPercent(Percent: Number) // Terrain-grass player-bend strength in percent (100 = default parting, 0 = grass stays still around players - stealth/hide-in-grass; clamped 0..200) [global: all clients]
1668
- SetGrassBladeWidthPercent(Percent: Number) // Built-in grass blade/tuft width in percent (100 = default, clamped 10..400). Width already auto-follows height so the texture never stretches; this is the extra wispy-vs-lush override [global: all clients]
1681
+ SetGrassBladeWidthPercent(Percent: Number) // Built-in grass blade/tuft width in percent (100 = default, clamped 10..400). Width already auto-follows height so the texture never stretches; blade count is SetGrassBladeDensityPercent [global: all clients]
1682
+ SetGrassBladeDensityPercent(Percent: Number) // Built-in tuft cards: percent of each tuft's blades that stand (100 = default, clamped 10..100); lower = blades farther apart, wispier. Width is SetGrassBladeWidthPercent [global: all clients]
1669
1683
  Object.SetUiDraggable(IsOn: Bool) // UI element only. Enable engine drag: press + small move starts a drag (UiDragStart), release fires UiDrop; a real drag suppresses ClickButton. Markup equivalent: draggable=true
1670
1684
  ItemName(Item: Object) -> Text // Item def's display name (Item.X handle); "" if not an item
1671
1685
  ItemTip(Item: Object) -> Text // Item def's tooltip text; "" if not an item
@@ -1702,16 +1716,16 @@ Text.Length() -> Number // Character count (chars, not bytes - Thai etc. count p
1702
1716
  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
1717
  Text.Contains(Needle: Text) -> Bool // True when the text contains Needle (case-sensitive; empty Needle -> true).
1704
1718
  Text.Replace(From: Text, To: Text) -> Text // Every occurrence of From replaced by To; empty From returns the text unchanged.
1705
- SetOcclusionCulling(Enabled: Bool) // Default: false (experimental Hi-Z occlusion culling)
1719
+ SetOcclusionCulling(Enabled: Bool) // Default: true (Hi-Z occlusion culling on static and skinned draws; false disables) [global: all clients]
1706
1720
  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)
1707
1721
  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]
1708
1722
  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
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
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)
1723
+ 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 [global: all clients]
1724
+ 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) [global: all clients]
1711
1725
  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
1712
1726
  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
1713
1727
  SetDefaultFloatingTextFont(Font: Object) // World default font for SpawnFloatingText/SpawnDamageNumber (e.g. Ori.font.google.Lobster); None reverts to built-in
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]
1728
+ SetWaterEnabled(Enabled: Bool) // THE main water: global displaced ocean surface (Gerstner waves) at the SetWaterSeaLevelCm 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]
1715
1729
  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]
1716
1730
  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]
1717
1731
  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)
@@ -1727,7 +1741,7 @@ Object.SetCharacterOverlapIgnoreTag(Tag: Number, IsOn: Bool) // This character's
1727
1741
  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
1728
1742
  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
1729
1743
  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
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)
1744
+ 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). The child's CENTER lands on the anchor (not its bottom-center pivot, not a grip socket); to mount by one of the CHILD's sockets use the 3-arg overload
1731
1745
  Object.Detach() // Remove this object's attachment; it keeps its current world transform. Refused for seated players (UnseatPlayer restores their physics)
1732
1746
  Object.GetAttachParent() -> Object // Attachment parent, or None-like non-existing Object when not attached (check with Exists())
1733
1747
  EaseIn(T: Number) -> Number
@@ -1740,7 +1754,7 @@ Object.RemoveTag(Tag: Number) // Clear one tag from this object (AddTag's invers
1740
1754
  Object.AttachToPositionOnly(Parent: Object) // AttachTo variant that follows Parent's POSITION only (captures the current offset in Parent's local frame, so it still swings with Parent's rotation) while this object's own rotation stays script-driven (SetRotation/LookAt keep working). For children that aim independently: beam FX, turret heads, billboards
1741
1755
  SetWaterChurnEffect(Effect: Object) // Replace the built-in bow-spray churn mist that trails fast swimmers/waders world-wide. Pass an Effect asset. Visual only; the built-in mist is used until this is called
1742
1756
  SetWaterFootstepEffect(Effect: Object) // Replace the built-in shallow-water footstep splash (expanding ring + puffs + droplets) world-wide. Pass an Effect asset. Visual only; the built-in is used until this is called
1743
- Object.SetWaterFlow(SpeedMps: Number, ForcedFall: Bool) // Water-object only. Stream/fall flow: SpeedMps (0 = still; quantized 0.025, max 3.175) advects refraction+foam along the mesh's world local +X axis - rotate the mesh to steer. ForcedFall=true applies the falling-water (waterfall) look to the whole mesh for fountain jets/sheets; steep surfaces get it automatically
1757
+ Object.SetWaterFlow(SpeedMps: Number, ForcedFall: Bool) // Water-object only. Stream/fall flow: SpeedMps (0 = still; quantized 0.025, max 3.175) advects refraction+foam along the mesh's world local +X axis - rotate the mesh to steer. ForcedFall=true is the only way to get the falling-water (waterfall) look - steep and rotated meshes stay still water until you ask
1744
1758
  Vector.Dot(Other: Vector) -> Number // Dot product (Number). 0 = perpendicular; with unit vectors, 1 = same direction, -1 = opposite
1745
1759
  Vector.Cross(Other: Vector) -> Vector // Cross product (Vector), right-handed: X.Cross(Y) = Z. Perpendicular to both inputs
1746
1760
  Vector.RotateByEuler(Yaw: Number, Pitch: Number, Roll: Number) -> Vector // Rotate this vector by euler degrees, standard ZYX order: yaw about Z, then pitch about Y, then roll about X (Z-up). Replaces hand-rolled rotation matrices
@@ -1795,7 +1809,7 @@ Object.RemoveInstance(Index: Number) // Batch only. Swap-remove: the LAST instan
1795
1809
  SetSkyboxColor(Color: Vector) // Procedural sky only. Base color multiplied into the sky bake before grading (alien/stylized skies); SetSkyboxTint post-multiplies the graded display instead. Default: (1, 1, 1) [global: all clients]
1796
1810
  GetRayHitDistance(Position: Position, Direction: Vector, Tag: Number) -> Number // Tag filters to objects WITH that tag; 0 is a real tag index, NOT "no filter" — use the 2-arg overload for unfiltered rays. Distance in cm to the first hit, -1 on miss — range gates and damage falloff.
1797
1811
  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.
1798
- SetSkyboxCloudType(Type: Number) // Procedural sky only. Cloud vertical form. Default: 0.35; 0 = flat haze layer, 1 = towering cumulus
1812
+ SetSkyboxCloudType(Type: Number) // Procedural sky only. Cloud vertical form. Default: 0.4; 0 = flat haze layer, 1 = towering cumulus [global: all clients]
1799
1813
  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
1800
1814
  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).
1801
1815
  GetRayHitPart(Position: Position, Direction: Vector) -> Text // Unfiltered twin: same part names and "" semantics as GetRayHitPart.
@@ -1847,6 +1861,8 @@ TileGridBakeCrowdObstacles(Grid: List)
1847
1861
  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>
1848
1862
  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)
1849
1863
  SetWaterMurkDensity(Density: Number) // Scales underwater-camera murk: 1 = default, 0 = crystal clear (underwater/side-view games), max 4. [global: all clients]
1864
+ SetWaterSeaLevelCm(SeaLevelCm: Number) // Ocean sea level (world Z cm). Hand-tuned constants belong in its #adjuster knob line; call this only to move the level at runtime (tides/floods). Default: 0 [global: all clients]
1865
+ Object.SetWaterShader(Material: Object) // Water-object only (SetIsWater). This mesh's own water shading: a #shader Material whose body defines fn shade_water (same fork contract as SetWaterShader); its mat.params4 are that material's SetMaterialParameterNumber slots. None = back to the global SetWaterShader / engine water. Ocean and voxel water take only the global override
1850
1866
  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
1867
  TileGridFindPath4NextTo(Grid: List, StartX: Number, StartY: Number, GoalX: Number, GoalY: Number) -> List
1852
1868
  TileGridFindPath8NextTo(Grid: List, StartX: Number, StartY: Number, GoalX: Number, GoalY: Number) -> List
@@ -1870,10 +1886,13 @@ VoxelSetHeightBox(Min: Position, Max: Position, Height: Number) -> Number // Vox
1870
1886
  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
1887
  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
1888
  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)
1889
+ Object.SetUiOffset(X: Number, Y: Number, Seconds: Number, Ease: Number, Delay: Number) // Delay: seconds before it starts; queues behind a running glide on the same property (one queued glide per property; SetUiSize/SetUiOpacity/SetUiRotation have the same form)
1873
1890
  Object.SetUiSize(Width: Number, Height: Number, Seconds: Number)
1874
1891
  Object.SetUiSize(Width: Number, Height: Number, Seconds: Number, Ease: Number)
1892
+ Object.SetUiSize(Width: Number, Height: Number, Seconds: Number, Ease: Number, Delay: Number)
1875
1893
  Object.SetUiOpacity(Opacity01: Number, Seconds: Number)
1876
1894
  Object.SetUiOpacity(Opacity01: Number, Seconds: Number, Ease: Number)
1895
+ Object.SetUiOpacity(Opacity01: Number, Seconds: Number, Ease: Number, Delay: Number)
1877
1896
  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
1897
  Object.GetUiOffsetX() -> Number // Current offset in base units (mid-glide = the in-flight value); GetUiSizeX/Y same for size
1879
1898
  Object.GetUiOffsetY() -> Number
@@ -1882,7 +1901,11 @@ Object.GetUiSizeY() -> Number
1882
1901
  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
1902
  Object.SetUiRotation(Degrees: Number, Seconds: Number)
1884
1903
  Object.SetUiRotation(Degrees: Number, Seconds: Number, Ease: Number)
1904
+ Object.SetUiRotation(Degrees: Number, Seconds: Number, Ease: Number, Delay: Number)
1885
1905
  Object.GetUiRotation() -> Number // Current rotation in degrees (mid-glide = in-flight value)
1906
+ Object.SetUiBorder(Width: Number, Color: Vector) // Runtime border (base units + rgb); width 0 clears
1907
+ Object.SetUiCornerRadius(Radius: Number) // Runtime corner radius (base units)
1908
+ Object.GetUiName() -> Text // The element's own name; count copies are <Name>0..N-1
1886
1909
  TileGridCreateFromTerrain(TilesX: Number, TilesY: Number, OriginTile: Vector, MaxSlope01: Number, MaxStepCm: Number) -> List
1887
1910
  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
1911
  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.
@@ -1908,11 +1931,14 @@ GetWaterAmountRect(X1: Number, Y1: Number, X2: Number, Y2: Number) -> Number //
1908
1931
  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
1932
  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
1933
  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
1934
+ 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. Per placed-water mesh: Object.SetWaterShader. See shader_dsl skill
1912
1935
  Object.MoveAlongTileFlowContinuous(Field: List, Grid: List, Speed: Number) -> Bool
1913
1936
  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
1937
+ 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. Draws the saved objects with their own materials and alpha (alpha < 1 = dithered ghost, no shadow); WithSizeAndColor tints them
1938
+ 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. Draws the saved objects with their own materials and alpha (alpha < 1 = dithered ghost, no shadow); WithSizeAndColor tints them
1939
+ SetWaterCausticIntensity(Scale: Number) // Scales the caustic light ripples on ground under the ocean: 1 = default, 0 = off, max 3. Knob-able (Find > Water). [global: all clients]
1940
+ SetBodylessMoveDeferred(Deferred: Bool) // Rollout switch, call once in OnSpawned: true = objects WITHOUT a physics body (display/UI markers) apply Move*Continuous* at the next physics step exactly like body-backed movers, so a marker and a player driven by identical calls stay aligned (same-tick GetPosition reads pre-move for both). Default false = legacy immediate translate.
1941
+ SetDLSS5(Enabled: Bool) // Default: false. Toggles the host-configured ONNX scene model in browser builds with TAAU; unsupported clients keep ordinary rendering. No NVIDIA DLSS 5 model is bundled
1916
1942
 
1917
1943
  </built_in_function_list>
1918
1944
 
@@ -1921,7 +1947,7 @@ DrawPreviewForPlayerWithSizeAndColor(PlayerIndex: Number, SavedObject: Object, P
1921
1947
  - 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).
1922
1948
  - 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.
1923
1949
  - 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.
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.
1950
+ - 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, SetGrassBladeDensityPercent, SetGrassWavePeriodCm, SetGrassWindStrengthCm, SetGrassWindSpeedPercent.
1925
1951
  - 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.
1926
1952
  Example (hills + deterministic tree scatter):
1927
1953
  #class WorldGen
@@ -1989,6 +2015,9 @@ height[, seed]` - signed relief composed into the base BEFORE erosion, so gullie
1989
2015
  through them like native terrain (flats/carves apply after and win locally).
1990
2016
  Line landform: `ridge_path = width, height, x0, y0, x1, y1, ...` - a noise-varied crest
1991
2017
  along the polyline (mountain ranges), eroded like other landforms; `smooth` applies too.
2018
+ `landform_blend = max` (header key; default add): overlapping landforms and ridges keep the
2019
+ highest relief (deepest for craters) instead of summing - stack shapes without deforming
2020
+ ones that already read right.
1992
2021
  Layer rows (same grammar/cap): `noise = height_m, size_m, octaves, gain, seed[, mode]` -
1993
2022
  additive base-noise layer; stack several sizes for multi-scale relief (continents + hills +
1994
2023
  detail). mode = TerrainHeightsNoise's table (0 fbm, 1 ridged, 2 billow, 3 voronoi, 5 spike
@@ -2030,10 +2059,15 @@ VoxelSet / VoxelFillBox / VoxelFillSphere edit blocks (Id 0 = air, N = N-th surf
2030
2059
  reads one; height tools overwrite only the span they move, so caves below stay. Without
2031
2060
  overhangs the block calls are errors. RaycastPosition hits block faces: hit +
2032
2061
  Direction.GetNormal() is inside the block, hit - Direction.GetNormal() is the cell to place on.
2033
- Same session-overlay rules (TerrainOverlayClear resets).
2062
+ Cells span [k*GridSize, (k+1)*GridSize) from Origin; SnapToVoxel returns the cell CENTER (the VoxelGet/VoxelSet key); GroundHeightAtXY is block-snapped.
2063
+ Same session-overlay rules (TerrainOverlayClear resets). Block art: a top-level
2064
+ `#terrain_surfaces <Name> ... #end` block holds up to 8 `surface = <albedo>, <normal>, <tiling_m>[, <orm>[, <grass01>]]`
2065
+ lines with UNQUOTED urls (`baked.<Texture>.color/.normal/.orm` for #texture bakes); block ids and
2066
+ block_strata numbers are its 1-based line order; bind it with terrain.SetTerrainSurfaces("<Name>").
2067
+ Layers resample into one fixed 1024 px array (512 on web); a `filter = nearest` bake keeps hard texels there too.
2034
2068
  - 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.
2035
2069
  - 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).
2070
+ - Water: sea/lakes at one level -> SetWaterEnabled(true) + SetWaterSeaLevelCm(seaLevelCm); pools/streams/falls at other heights -> flat SetIsWater(true) objects (SetWaterFlow steers rivers/falls).
2037
2071
 
2038
2072
  DATA exception lane - TerrainHeights* ops on a heights List -> SpawnTerrainFromHeights
2039
2073
  (bounded baked grid). Use ONLY when the look needs grid-global ops (routed drainage,
@@ -2086,6 +2120,43 @@ Two terrain display types:
2086
2120
  Grass density likewise: SetTerrainGrassSpawnMaterial with a custom spawn compute #shader.
2087
2121
  </terrain_generation>
2088
2122
 
2123
+ <field_maps>
2124
+ #fieldmap <Name> ... #end (top level): a persistent GPU data map over the world around
2125
+ the camera, stamped from scripts and evolved per frame - carved snow/sand/mud trails,
2126
+ footprints, scorch marks, wetness, crushed grass, gust fields. Display-only: gameplay
2127
+ can never read it back (spatial logic belongs in TileGrid state).
2128
+ Keys (all optional - a bare block is a working stamp-accumulate map): resolution = 256
2129
+ (pow2 64..1024), window_m = 120 (world window following the camera), format = rgba8 |
2130
+ rgba16f, heal_seconds = 0 (default kernel: stamps fade back over this; 0 = permanent).
2131
+ Max 2 per world; starts at zero.
2132
+ FieldStamp(Name, Position, RadiusCm, Strength): press one mark this frame (cap 64/frame,
2133
+ nearest camera win; negative Strength erases). Calling it every frame per moving entity
2134
+ is the intended use. FieldStampCapsule(Name, From, To, RadiusCm, Strength): the same as a
2135
+ flat-floored segment (From = last frame's position) - continuous furrows without disc
2136
+ scallops.
2137
+ The default kernel needs no shader (max-accumulate into r, g = the pushed-up rim just
2138
+ outside each press, b = seconds since the last press; walls settle only for their first
2139
+ seconds, heal refills). Custom dynamics
2140
+ (ripples, spread, wind advection): SetFieldMaterial(Name, Material.M) - M's #shader body
2141
+ defines fn ori_field_update(ctx: OriFieldCtx) -> vec4<f32> (whole-function hook like
2142
+ surface weights, no own bindings; fork the stock body: read_shader
2143
+ Ori.shader.builtin.fieldmap_default). ctx: prev (vec4, zero on texels new to the window),
2144
+ world_pos (render XZ meters), uv, texel, dt_s, time_s; u_field.params4 = the material's
2145
+ SetMaterialParameterNumber slots; u_field.heal_per_s = the block's heal rate. Helpers:
2146
+ ori_field_stamp_press(ctx.world_pos) (x = press sum, y = rim band outside the presses;
2147
+ ori_field_stamp_sum = .x), ori_field_prev_load(texel) (toroidal neighbor read). The kernel
2148
+ owns all dynamics.
2149
+ Sample it anywhere: bind Ori.image.engine.fieldmap.<name> into a material texture slot
2150
+ (material { texN = } or SetMaterialParameterTexture; terrain materials: slots 0-12) and
2151
+ read it in any #shader: uv = ctx.world_pos.xz / window_m (sampler wraps;
2152
+ textureSampleLevel in @vertex; keep window_m as a shader const matching the block).
2153
+ Ground dents: @vertex ctx.world_pos.y += g * rim_m - r * depth_m; normal via
2154
+ ori_height_to_normal_w, crisp walls via ori_relief_march, a shadow side via
2155
+ ori_relief_shadow into out.orm.x. Debug: show the live map in a UI image
2156
+ (image = "ori.image.engine.fieldmap.<name>"). See <example_snow_trails>.
2157
+ Off on the Minimal quality tier (map freezes at zero - design looks to degrade).
2158
+ </field_maps>
2159
+
2089
2160
  <procedural_generation_batches>
2090
2161
  - 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.
2091
2162
  - 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.
@@ -2112,12 +2183,12 @@ for i in 0..2000 {
2112
2183
  - Only one instance of each event_type+event_options is allowed per class. Duplicates are a compile error.
2113
2184
  - 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.
2114
2185
  - 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.
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").
2186
+ - GameCommand fires once per live instance of every class that declares it when a user sends a command text message (chat /cmd, or headless .order lines, which arrive as player 0). Event.Text is the raw command line, Event.Sender the sending player's object: `if Event.Sender != Self { return }` passes only in that player's own player-class handler - never guard a manager/HUD handler on it. Parse Text yourself (e.g. "build house 3 4").
2116
2187
  - 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.
2117
2188
  - 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
2189
  - 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
2190
  - 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.
2191
+ - Broadcast events without ByThisPlayer (LeftClickThisWorld, LeftMouseClick, GameCommand, ...) 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.
2121
2192
  </events>
2122
2193
  <event_list>
2123
2194
  event OnPreSpawn
@@ -2326,7 +2397,7 @@ event ActionInvokedByThisPlayer[Primary]
2326
2397
  - Inventories/hotbars/chests: see <inventory> — item clauses + BindInventory give a fully engine-synced slot UI.
2327
2398
  - 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.
2328
2399
  - ui object tags for world description: screenCanvas/worldCanvas/frame/textLabel/button; elements nest
2329
- - Element handles in script: scene-declared elements resolve as Scene.X; children of a SpawnCanvas canvas via canvas.FindChildUI("Name")
2400
+ - Element handles in script: scene-declared elements resolve as Scene.X; children of a SpawnCanvas canvas via canvas.FindChildUI("Name"); GetUiName() returns an element's own name (count copies: <Name>0..N-1).
2330
2401
  - 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.
2331
2402
  - worldCanvas = screenCanvas anchored to a world position (position = (x, y, z)): children use the same base-unit offsets around the projected anchor and keep constant on-screen size (nameplates, above-object markers); draws under screenCanvas; move the canvas object to follow things
2332
2403
 
@@ -2347,22 +2418,23 @@ event ActionInvokedByThisPlayer[Primary]
2347
2418
  - 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.
2348
2419
  - 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.
2349
2420
  - 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.
2421
+ - 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. A cover root is a plain frame: set color=#00000000 or it paints the screen black.
2351
2422
  - visible = false starts any element hidden (there is no `hidden` field); SetVisible(true) reveals it - same flag.
2352
2423
  - 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).
2353
2424
  - 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.
2354
2425
  - 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).
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.
2426
+ - 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). Runtime: SetUiBorder(Width, Color) (width 0 clears) and SetUiCornerRadius(R); gradients and hover_* stay authoring-only.
2356
2427
  - progressBar: color = fill, background_color = track; fill ratio via SetProgress(Value, MaxValue).
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.
2428
+ - 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. Delay (4th/5th arg on the Ease forms) = seconds before the glide starts; a delayed glide queues behind a running one on the same property (fade in, then a delayed fade out = a banner); an instant set cancels both. SetUiColor has no Ease/Delay form.
2358
2429
  - 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.
2430
+ - rotation = deg (any element; SetUiRotation(Deg[, Secs[, Ease[, Delay]]]), 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
2431
  - 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.
2361
2432
  - 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.
2362
2433
  - 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).
2434
+ - 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). image also accepts material = "M" (the art becomes tex0); sheet/nine_slice/flip_x are ignored there.
2435
+ - image = "baked.<Name>.color" draws a #texture bake or a #svg drawing as UI art (same form for item icons; a #svg keeps its viewBox aspect - size the element to match); texture.<Name>.color is the #particles form and draws nothing here.
2364
2436
  - 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.
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.
2437
+ - 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 or #svg drawings.
2366
2438
  - 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).
2367
2439
  - Colors accept optional 8-digit hex alpha (e.g. #3A3A3AFF) in addition to #RRGGBB and (r,g,b).
2368
2440
  - 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).
@@ -2380,7 +2452,7 @@ event ActionInvokedByThisPlayer[Primary]
2380
2452
  <inventory>
2381
2453
  - Weave2-only engine inventory system. ALWAYS prefer it over hand-rolled slot vars/slot buttons for hotbars, backpacks, chests, sell-targets.
2382
2454
  - Define item types as top-level clauses (placeable like `object`): item hoe { name="Hoe" tip="Till soil." icon="image.<owner>.<name>" tint=(0.7,0.7,0.9) stack=99 value=2 }
2383
- All properties optional; defaults: name = declared name, tip "", no icon, tint white, stack 1, value 0. icon is a QUOTED image url string. Duplicate item names = load error.
2455
+ All properties optional; defaults: name = declared name, tip "", no icon, tint white, stack 1, value 0. icon is a QUOTED image url string (`image.*`, `alias.image.*`, or a #texture bake `baked.<Texture>.color`; `texture.*` is the particle form and draws nothing here). Duplicate item names = load error.
2384
2456
  - Item.<declname> is a compile-time item handle (opaque Object; unknown name = link error). Read def properties anywhere: ItemName(i)/ItemTip(i) -> Text, ItemIcon(i) -> Object, ItemTint(i) -> Vector, ItemStack(i)/ItemValue(i) -> Number.
2385
2457
  - var inv = CreateInventory(Slots) (1..128): deterministic container owned by the calling object (owner despawn frees it). Make inventories per player (player OnSpawned). Methods on the handle:
2386
2458
  inv.AddItem(item, n) -> overflow Number (fills existing stacks first; 0 = all fit) | inv.RemoveItem(item, n) -> Bool (exact-or-nothing) | inv.CountOf(item)
@@ -2427,19 +2499,19 @@ event InventoryDropOnWorld {
2427
2499
  - use the function PlayAnimationOnRepeat to loop animations like run forever
2428
2500
  - PlayAnimationOnRepeat(Ori.anim.default.goblin.run)
2429
2501
  - PlayAnimation(Ori.anim.default.goblin.attack)
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
2502
+ - 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: Root, Spine, Torso, Neck, Head, Scapula/UpperArm/Forearm/Hand, Thigh/Shin/Foot/Toe with L/R suffixes, e.g. UpperArmR) adds or shadows one for this world
2431
2503
  - 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.
2432
2504
 
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
2505
+ - Compatible humanoid rigs can play `Ori.anim.canon.Name` and mapped humanoid clips through display-only retargeting. Library imports check the target rig and pin the selected motion.
2434
2506
  - `#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
2435
2507
 
2436
2508
  - 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)
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
2509
+ - the clip is picked automatically: .idle when still, .run when moving (falling back to .walk), .jumpHover when airborne, .sit when seated (SeatPlayer; a model's own .sit beats the built-in canonical sit - the seat point is the contact surface and the sit clip's root offset lifts the hips a pelvis height above it, canon +5cm scaled per rig; author own sit clips with offset: (0, 0, 5)); camera-locked players also use .runBack/.runLeft/.runRight by movement direction
2438
2510
  - using PlayAnimation will stop these animations until the animation is finished
2439
2511
  - 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
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
2512
+ - Rigged humanoid models (store characters or DSL rigs) that ship no `.idle` clip idle/walk/run/sit (jumpHover airborne) with the built-in canonical clips automatically (display-only retarget); models with their own clips always use them
2441
2513
  - 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
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:
2514
+ - 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 (SetBoneRotation on imported rigs only; canonical #clip keys mirror with OPPOSITE X/Z, same Y). Head-track + breathe over auto-walk:
2443
2515
  forever {
2444
2516
  BoneLookAt("Head", Friend.GetCenterPosition(), 55, 240)
2445
2517
  SetBoneRotationMix("Torso", Vector(0, 2, 0), 1, 0.7, 1) // additive lean; alternate targets on a timer to breathe
@@ -3418,7 +3490,7 @@ event OnSpawned {
3418
3490
  SetFogEndDistance(2800)
3419
3491
  SetSunTilt(33)
3420
3492
  SetSunRotation(35)
3421
- SetWaterEnabled(true, 0)
3493
+ SetWaterEnabled(true)
3422
3494
  SetWaterWaves(5, 800, 0.3, 0, 100)
3423
3495
  }
3424
3496
  #end
@@ -3729,6 +3801,232 @@ saved StyledHud {
3729
3801
  </example_saved_block_font>
3730
3802
 
3731
3803
 
3804
+ <example_snow_trails>
3805
+ // snow_trails: a snowy mountain where walking presses real boot prints into the snow -
3806
+ // the #fieldmap dynamic-canvas primitive end to end (see <field_maps>).
3807
+ // Pattern notes:
3808
+ // - #fieldmap SnowTrails runs the DEFAULT kernel (no shader needed): FieldStampBoot presses
3809
+ // an oriented heel+toe print with a sole tread into r once per stride, the snow it pushed
3810
+ // aside piles up in g just outside the outline, walls settle for a few seconds then hold,
3811
+ // heal_seconds refills them like fresh snowfall. Prints want <= 2.5 cm per texel for the
3812
+ // tread to read: 1024 texels over a 24 m window (2.3 cm). The other pattern - a per-frame
3813
+ // round FieldStamp trench - wants a 100 m window instead.
3814
+ // Custom dynamics (ripples, spread): SetFieldMaterial + fork read_shader
3815
+ // Ori.shader.builtin.fieldmap_default.
3816
+ // - The ground samples the map as a plain texture: material tex3 =
3817
+ // "Ori.image.engine.fieldmap.snowtrails", uv = world_xz_m / WINDOW_M (keep WINDOW_M as
3818
+ // a shader const matching the block; the material sampler wraps, matching the map's
3819
+ // toroidal window). @vertex sinks the dent and raises the rim; the fragment walks the eye
3820
+ // ray through the print (ori_relief_march: near-camera terrain quads are 12.5 cm and a
3821
+ // print is 28 cm, so the fragment still has to carry the wall), re-derives the normal from
3822
+ // both channels (ori_height_to_normal_w), shades the floor beside the sun-facing wall
3823
+ // (ori_relief_shadow into occlusion) and tints the compacted snow - lightly: snow floors
3824
+ // stay bright (the engine snow class floors its ambient occlusion for the same reason).
3825
+ // - Snow clods: a #mesh lump as a "mesh" particle component with groundBounce, a burst of a
3826
+ // few per stride kicked off the print rim. A continuous furrow (wading) is one
3827
+ // FieldStampCapsule per moving frame from last frame's position instead of boot prints.
3828
+ // - Lighting recipe for snow: a warm low sun with a blue sky fill - snow shadows are lit by
3829
+ // the sky, so they read blue against the warm sunlit side.
3830
+ // - Debug trick: show the live map in a UI image (image = "ori.image.engine.fieldmap.snowtrails").
3831
+
3832
+ #terrain SnowMountain
3833
+ mound_height_m = 260
3834
+ mound_size_m = 420
3835
+ #end
3836
+
3837
+ #fieldmap SnowTrails
3838
+ resolution = 1024
3839
+ window_m = 24
3840
+ heal_seconds = 300
3841
+ #end
3842
+
3843
+ #shader SnowGround
3844
+ @module {
3845
+ const WINDOW_M: f32 = 24.0; // = #fieldmap window_m
3846
+ const TEXEL_UV: f32 = 1.0 / 1024.0; // = 1 / #fieldmap resolution
3847
+ const TRAIL_DEPTH_M: f32 = 0.12; // print depth (map r = 1)
3848
+ const RIM_M: f32 = 0.03; // pushed-up rim height (map g = 1)
3849
+ // Chewed snow: world noise breaks the map values up so print edges and rims read as
3850
+ // churned crystals, not clean cuts. One fn keeps VS and FS in exact agreement.
3851
+ fn chew(wp: vec2<f32>) -> f32 {
3852
+ return 0.8 + 0.4 * ori_fbm2(wp.x * 5.0, wp.y * 5.0, 2, 21u);
3853
+ }
3854
+ // Print relief in meters: the dent (r, eased so lip and floor round off like packed
3855
+ // snow) sinks, the rim (g) rises and crumbles a little harder.
3856
+ fn relief_at(duv: vec2<f32>, wp: vec2<f32>) -> f32 {
3857
+ let m = textureSampleLevel(tex3, tex_sampler, duv, 0.0);
3858
+ let c = chew(wp);
3859
+ let sink = m.r * m.r * (3.0 - 2.0 * m.r);
3860
+ return m.g * RIM_M * (0.75 + 0.5 * c) - sink * TRAIL_DEPTH_M * c;
3861
+ }
3862
+ // Untouched-snow relief: broad wind sastrugi + fine ripple, shading-only (finite
3863
+ // differences of this feed the fragment normal - vast flat white reads CG-sterile).
3864
+ fn snow_h(wp: vec2<f32>) -> f32 {
3865
+ return ori_fbm2(wp.x * 0.055, wp.y * 0.055, 3, 5u) * 2.2
3866
+ + ori_fbm2(wp.x * 0.85, wp.y * 0.85, 2, 9u) * 0.16;
3867
+ }
3868
+ }
3869
+ @vertex {
3870
+ ctx.world_pos.y += relief_at(ctx.world_pos.xz / WINDOW_M, ctx.world_pos.xz);
3871
+ }
3872
+ let vdir = normalize(u_camera.camera_position - ctx.world_pos);
3873
+ // Parallax: walk the eye ray through the print (8 steps + bisection) so walls keep their
3874
+ // depth between terrain vertices; the map stores depth, so the sample slides away from the eye.
3875
+ let uv0 = ctx.world_pos.xz / WINDOW_M;
3876
+ let ray = vdir.xz / max(vdir.y, 0.25) / WINDOW_M;
3877
+ let duv = ori_relief_march(tex3, tex_sampler, uv0, ray, TRAIL_DEPTH_M, 8);
3878
+ let m = textureSampleLevel(tex3, tex_sampler, duv, 0.0);
3879
+ let trail = m.r * chew(ctx.world_pos.xz);
3880
+ let rim = m.g;
3881
+ // Print relief before the color: dent + rim normal in one weighted call (r = depth, g = rim)
3882
+ // and how steep the wall is here - walls and tread ridges are where a print reads as deep.
3883
+ let dent_n = ori_height_to_normal_w(tex3, tex_sampler, duv, TEXEL_UV, WINDOW_M / 1024.0,
3884
+ vec4<f32>(-TRAIL_DEPTH_M, RIM_M, 0.0, 0.0));
3885
+ let wall = clamp(length(dent_n.xz) * 1.6, 0.0, 1.0);
3886
+ // Horizon shadow toward the sun: the sun-facing wall shades the floor beside it. Carried in
3887
+ // occlusion (the engine snow class takes half of it out of the direct sun as well).
3888
+ let sun = u_camera.sun_direction.xyz;
3889
+ let sun_uv = -sun.xz / max(-sun.y, 0.05) / WINDOW_M;
3890
+ let lit = ori_relief_shadow(tex3, tex_sampler, duv, sun_uv, TRAIL_DEPTH_M, 6);
3891
+ // Bright, faintly blue snow with fine grain; pressed prints read as compacted, icy blue.
3892
+ let grain = ori_fbm2(ctx.world_pos.x * 3.0, ctx.world_pos.z * 3.0, 3, 11u);
3893
+ var snow = vec3<f32>(0.90, 0.93, 0.98) * (0.92 + grain * 0.1);
3894
+ // Compacted snow stays bright: snow scatters light back into its own cavities, so the floor
3895
+ // only cools a little toward icy blue. Shape comes from the normal and the rim, not from albedo.
3896
+ snow = mix(snow, vec3<f32>(0.72, 0.80, 0.94), trail * 0.45);
3897
+ snow *= 1.0 - wall * 0.03 * trail;
3898
+ // The rim is loose pushed powder: a touch brighter and warmer where it catches the sun.
3899
+ snow += vec3<f32>(0.06, 0.055, 0.04) * rim;
3900
+ // (No hand-rolled sparkle: `lighting = "snow"` on the material brings engine wrap
3901
+ // lighting, backscatter, grain and ice glints - albedo/normal/roughness stay this shader's job.)
3902
+ out.base_color = vec4<f32>(snow, 1.0);
3903
+ // Normals: dune/ripple relief on open snow, the print dent + rim, and crumbled-chunk jitter
3904
+ // on the rim, all as finite-difference perturbations of the terrain normal.
3905
+ let e = 0.35;
3906
+ let shx = snow_h(ctx.world_pos.xz + vec2<f32>(e, 0.0)) - snow_h(ctx.world_pos.xz - vec2<f32>(e, 0.0));
3907
+ let shz = snow_h(ctx.world_pos.xz + vec2<f32>(0.0, e)) - snow_h(ctx.world_pos.xz - vec2<f32>(0.0, e));
3908
+ let chunk = vec2<f32>(ori_fbm2(ctx.world_pos.x * 9.0, ctx.world_pos.z * 9.0, 2, 31u),
3909
+ ori_fbm2(ctx.world_pos.x * 9.0 + 9.0, ctx.world_pos.z * 9.0, 2, 37u)) - vec2<f32>(0.5);
3910
+ out.world_normal = normalize(ctx.normal
3911
+ + (dent_n - vec3<f32>(0.0, 1.0, 0.0)) * 1.3
3912
+ + vec3<f32>(chunk.x, 0.0, chunk.y) * rim * 0.3
3913
+ + vec3<f32>(-shx, 0.0, -shz) * (0.30 * (1.0 - trail)));
3914
+ // Grazing sheen: fresh powder throws a soft low-sun sparkle band at glancing angles.
3915
+ let fres = pow(1.0 - clamp(dot(out.world_normal, vdir), 0.0, 1.0), 3.0);
3916
+ let rough = mix(mix(0.55, 0.30, fres * (1.0 - trail)), 0.22, trail);
3917
+ // Occlusion: a light touch on the floor (snow refills its cavities with scattered light),
3918
+ // the shadow side loses some sun; the engine snow class takes part of it out of direct light.
3919
+ out.orm = vec4<f32>(clamp(1.0 - trail * 0.12, 0.0, 1.0) * mix(0.85, 1.0, lit), rough, 0.0, 0.0);
3920
+ #end
3921
+
3922
+ material SnowGroundMat {
3923
+ shader = "SnowGround"
3924
+ tex3 = "Ori.image.engine.fieldmap.snowtrails"
3925
+ lighting = "snow" // engine snow response: wrapped terminator, backscatter, grain, glints
3926
+ }
3927
+
3928
+ // Snow clods: a small lump (3 cm, "Snow" material) thrown as lit mesh particles that bounce
3929
+ // and settle on the ground - a few per stride off the print rim.
3930
+ #mesh SnowClod
3931
+ let base = scale(sphere(r: 3, segments: 6, material: "Snow"), (1.3, 0.8, 1.0));
3932
+ out.geo = displace(base, amount: 1.2, seed: 7);
3933
+ #end
3934
+
3935
+ #particles SnowClods
3936
+ [
3937
+ { "type": "mesh", "particleMeshName": "mesh.SnowClod", "boundingBoxSize": [1.6, 1.6, 1.0], "boundingBoxCenter": [0, 0, 0.3], "burstParticleCount": 5, "spawnRate": 0, "duration": 0.1, "isLooping": 0, "lifeTimeRange": [1.2, 2.0], "emitterShapeType": "sphere", "shapeStyle": "volume", "shapeInOut": "out", "radius": 0.08, "speedRange": [0.6, 1.6], "spreadAngle": [180, 60], "sizeConstant": 0.05, "opacitySequence": [[0, 1.0], [0.8, 1.0], [1, 0.0]], "accelerationVector": [0, 0, -9.8], "drag": 0.0, "groundBounce": 0.25, "tumbleSpeedRange": [120, 480] }
3938
+ ]
3939
+ #end
3940
+
3941
+ #class WorldGen
3942
+ event OnSpawned {
3943
+ // 100cm tiles: finest terrain quads ~25cm, so print rims displace cleanly.
3944
+ var terrain = SpawnTerrainRecipe(100, Origin())
3945
+ terrain.SetTerrainMaterial(Material.SnowGroundMat)
3946
+ terrain.SetTerrainGrassVisible(false) // bare snow - no meadow over the slopes
3947
+ // Snow lighting: a warm low sun (print walls and rims throw the shadows that sell the
3948
+ // relief) against a blue sky fill - snow shadows are sky-lit, so they read blue.
3949
+ SetSunTilt(18)
3950
+ SetSunRotation(120)
3951
+ SetSunColor(1.0, 0.94, 0.86)
3952
+ SetSunIntensity(3.6)
3953
+ SetSkyLightIntensity(2.0)
3954
+ SetSkyLightColor(Vector(0.80, 0.88, 1.0))
3955
+ SetFogEndDistance(2600)
3956
+ SetCameraFarDistanceM(3000)
3957
+ }
3958
+ #end
3959
+
3960
+ #class player
3961
+ event OnSpawned {
3962
+ // Spawn on the highest crest within ~50m so the slide lasts.
3963
+ var bestX = 0
3964
+ var bestY = 0
3965
+ var bestH = GroundHeightAtXY(0, 0)
3966
+ var probes: List<Number> = []
3967
+ probes.Add(5000); probes.Add(0); probes.Add(0 - 5000); probes.Add(0)
3968
+ probes.Add(0); probes.Add(5000); probes.Add(0); probes.Add(0 - 5000)
3969
+ probes.Add(3500); probes.Add(3500); probes.Add(0 - 3500); probes.Add(3500)
3970
+ probes.Add(3500); probes.Add(0 - 3500); probes.Add(0 - 3500); probes.Add(0 - 3500)
3971
+ for i in 0..8 {
3972
+ var x = probes.Get(i * 2)
3973
+ var y = probes.Get(i * 2 + 1)
3974
+ var h = GroundHeightAtXY(x, y)
3975
+ if h > bestH {
3976
+ bestH = h
3977
+ bestX = x
3978
+ bestY = y
3979
+ }
3980
+ }
3981
+ TeleportTo(Position(bestX, bestY, bestH + 200))
3982
+ var moving = false
3983
+ var stepDist = 0
3984
+ var lastPos = GetPosition()
3985
+ var side = 1
3986
+ forever {
3987
+ // Plain camera-relative walking; one print per 62 cm stride, alternating feet, offset
3988
+ // 11 cm to each side. No autonomous steering of any kind - the character does only
3989
+ // what the player does.
3990
+ var steer = GetMoveVectorCamera()
3991
+ if steer.Length() > 0.1 {
3992
+ MoveInDirectionContinuouslyFacing(steer, 700)
3993
+ var p = GetPosition()
3994
+ stepDist = stepDist + p.DistanceTo(lastPos)
3995
+ lastPos = p
3996
+ if stepDist >= 62 {
3997
+ stepDist = 0
3998
+ side = 0 - side
3999
+ var r = GetDirectionRight()
4000
+ var foot = Position(p.X + r.X * 11 * side, p.Y + r.Y * 11 * side, p.Z)
4001
+ FieldStampBoot("SnowTrails", foot, GetFacingDirection(), 28, 1, side)
4002
+ // A few clods kicked off the rim of the print that just landed.
4003
+ SpawnEffectWithAutoDestroy(Ori.effect.SnowClods, Position(foot.X, foot.Y, foot.Z + 4))
4004
+ }
4005
+ if not moving {
4006
+ moving = true
4007
+ // Feet sink a little into the powder while wading (display-only).
4008
+ SetRenderOffset(Vector(0, 0, 0 - 6), Vector(0, 0, 0), 1)
4009
+ }
4010
+ } else {
4011
+ if moving {
4012
+ moving = false
4013
+ SetRenderOffset(Vector(0, 0, 0), Vector(0, 0, 0), 1)
4014
+ }
4015
+ }
4016
+ }
4017
+ }
4018
+ event ActionDownByThisPlayer[Jump] {
4019
+ if IsOnGround() {
4020
+ Jump(1100)
4021
+ }
4022
+ }
4023
+ #end
4024
+
4025
+ display WorldGenObj : WorldGen { visible = false }
4026
+
4027
+ </example_snow_trails>
4028
+
4029
+
3732
4030
  <example_stardew_valley>
3733
4031
  // Stardew-like Farming Prototype: engine inventory (item clauses + BindInventory hotbar with
3734
4032
  // engine-side drag/swap), tools, crops, resources, drops, selling, energy/day cycle.
@@ -4519,286 +4817,286 @@ Drag items out of slots to sell or drop them"""
4519
4817
 
4520
4818
 
4521
4819
  <example_texture_dsl_desert_forest_demo>
4522
- // Procedural texture DSL demo: semi-realistic desert wall + forest ground.
4523
-
4524
- #texture DesertWall
4525
- tile_m = 2.0;
4526
- let tile = pattern.tile(uv * vec2(6.0, 4.0), mortar: 0.065, seed: 3);
4527
- let warp = noise.vec2(uv * 3.0, seed: 11, period: 3) * 0.18;
4528
- let cells = pattern.voronoi(uv * vec2(12.0, 8.0) + warp, jitter: 0.82, seed: 7, period: vec2(12.0, 8.0));
4529
- let cracks = stroke(cells.edge, width: 0.055, aa: 0.012);
4530
- let grain = noise.ridged(uv * 72.0, octaves: 3, seed: 19, period: 72);
4531
- let broad = noise.fbm(uv * vec2(6.0, 4.0), octaves: 4, seed: 23, period: vec2(6.0, 4.0));
4532
- let chips = smoothstep(0.65, 0.95, noise.ridged(uv * 36.0, octaves: 3, seed: 29, period: 36)) * (1.0 - smoothstep(0.02, 0.18, tile.edge));
4533
- let dust = levels(noise.fbm(uv * 5.0 + warp, octaves: 4, seed: 31, period: 5), black: 0.28, white: 0.86, gamma: 1.35);
4534
- let block = contrast(sat(broad * 0.65 + grain * 0.25 + rand(tile.id, seed: 5) * 0.10), amount: 1.25, pivot: 0.45);
4535
- let height = mix(-0.045, 0.11 + grain * 0.018 - chips * 0.06, tile.mask) - cracks * 0.07;
4536
- let stone = ramp(block, [0.0: color("#7d664a"), 0.35: color("#a8875a"), 0.75: color("#c9a66f"), 1.0: color("#ead19c")]);
4537
- let mortar = color("#75614b") * mix(0.82, 1.08, broad);
4538
- out.color = mix(mortar, stone, tile.mask) * mix(0.82, 1.06, dust) - cracks * 0.12;
4539
- out.normal = normal_from_height(height, strength: 1.05);
4540
- out.orm = orm(occlusion: ao_from_height(height, radius: 2.5), roughness: mix(0.94, 0.82, tile.mask) + dust * 0.03, metallic: 0.0);
4541
- #end
4542
-
4543
- #texture ForestGround
4544
- tile_m = 3.0;
4545
- let p = uv * vec2(8.0, 8.0);
4546
- let warp = noise.vec2(uv * 2.0, seed: 41, period: 2) * 0.25;
4547
- let soil = levels(noise.fbm(p + warp, octaves: 4, seed: 43, period: vec2(8.0, 8.0)), black: 0.18, white: 0.88, gamma: 1.2);
4548
- let stones = pattern.voronoi(uv * vec2(7.0, 7.0) + warp * 0.6, jitter: 0.78, seed: 47, period: vec2(7.0, 7.0));
4549
- let stone_shape = 1.0 - smoothstep(0.34, 0.47, stones.dist + noise.fbm(stones.local * 4.0, octaves: 2, seed: 53) * 0.08);
4550
- let cell_guard = smoothstep(0.025, 0.07, stones.edge);
4551
- let stone_mask = stone_shape * cell_guard;
4552
- let moss = levels(noise.fbm(uv * vec2(5.0, 5.0) + warp * 1.4, octaves: 4, seed: 59, period: 5) + (1.0 - stone_mask) * 0.25, black: 0.44, white: 0.92, gamma: 0.85);
4553
- let wet = contrast(noise.fbm(uv * 3.0 + warp, octaves: 3, seed: 61, period: 3), amount: 1.55, pivot: 0.48);
4554
- let height = soil * 0.045 + moss * 0.035 + sat(1.0 - stones.dist * 2.1) * stone_mask * 0.10 - (1.0 - soil) * 0.025;
4555
- let soil_color = ramp(soil, [0.0: color("#211912"), 0.45: color("#3b2b1d"), 1.0: color("#6c5538")]);
4556
- let moss_color = ramp(moss, [0.0: color("#26351d"), 0.55: color("#43622d"), 1.0: color("#7f9b55")]);
4557
- let stone_color = ramp(rand(stones.id, seed: 67), [0.0: color("#3e4039"), 0.5: color("#68675b"), 1.0: color("#918a78")]);
4558
- out.color = mix(mix(soil_color, moss_color, moss * 0.82), stone_color, stone_mask * 0.75);
4559
- out.normal = normal_from_height(height, strength: 0.95);
4560
- out.orm = orm(occlusion: ao_from_height(height, radius: 2.0), roughness: mix(0.92, 0.58, wet), metallic: 0.0);
4561
- #end
4562
-
4563
- object desert_wall {
4564
- model = "cube"
4565
- material = "DesertWall"
4566
- position = (0, -240, 160)
4567
- size = (700, 40, 320)
4568
- }
4569
-
4570
- object forest_ground {
4571
- model = "cube"
4572
- material = "ForestGround"
4573
- position = (0, 180, -20)
4574
- size = (900, 520, 40)
4575
- }
4820
+ // Procedural texture DSL demo: semi-realistic desert wall + forest ground.
4821
+
4822
+ #texture DesertWall
4823
+ tile_m = 2.0;
4824
+ let tile = pattern.tile(uv * vec2(6.0, 4.0), mortar: 0.065, seed: 3);
4825
+ let warp = noise.vec2(uv * 3.0, seed: 11, period: 3) * 0.18;
4826
+ let cells = pattern.voronoi(uv * vec2(12.0, 8.0) + warp, jitter: 0.82, seed: 7, period: vec2(12.0, 8.0));
4827
+ let cracks = stroke(cells.edge, width: 0.055, aa: 0.012);
4828
+ let grain = noise.ridged(uv * 72.0, octaves: 3, seed: 19, period: 72);
4829
+ let broad = noise.fbm(uv * vec2(6.0, 4.0), octaves: 4, seed: 23, period: vec2(6.0, 4.0));
4830
+ let chips = smoothstep(0.65, 0.95, noise.ridged(uv * 36.0, octaves: 3, seed: 29, period: 36)) * (1.0 - smoothstep(0.02, 0.18, tile.edge));
4831
+ let dust = levels(noise.fbm(uv * 5.0 + warp, octaves: 4, seed: 31, period: 5), black: 0.28, white: 0.86, gamma: 1.35);
4832
+ let block = contrast(sat(broad * 0.65 + grain * 0.25 + rand(tile.id, seed: 5) * 0.10), amount: 1.25, pivot: 0.45);
4833
+ let height = mix(-0.045, 0.11 + grain * 0.018 - chips * 0.06, tile.mask) - cracks * 0.07;
4834
+ let stone = ramp(block, [0.0: color("#7d664a"), 0.35: color("#a8875a"), 0.75: color("#c9a66f"), 1.0: color("#ead19c")]);
4835
+ let mortar = color("#75614b") * mix(0.82, 1.08, broad);
4836
+ out.color = mix(mortar, stone, tile.mask) * mix(0.82, 1.06, dust) - cracks * 0.12;
4837
+ out.normal = normal_from_height(height, strength: 1.05);
4838
+ out.orm = orm(occlusion: ao_from_height(height, radius: 2.5), roughness: mix(0.94, 0.82, tile.mask) + dust * 0.03, metallic: 0.0);
4839
+ #end
4840
+
4841
+ #texture ForestGround
4842
+ tile_m = 3.0;
4843
+ let p = uv * vec2(8.0, 8.0);
4844
+ let warp = noise.vec2(uv * 2.0, seed: 41, period: 2) * 0.25;
4845
+ let soil = levels(noise.fbm(p + warp, octaves: 4, seed: 43, period: vec2(8.0, 8.0)), black: 0.18, white: 0.88, gamma: 1.2);
4846
+ let stones = pattern.voronoi(uv * vec2(7.0, 7.0) + warp * 0.6, jitter: 0.78, seed: 47, period: vec2(7.0, 7.0));
4847
+ let stone_shape = 1.0 - smoothstep(0.34, 0.47, stones.dist + noise.fbm(stones.local * 4.0, octaves: 2, seed: 53) * 0.08);
4848
+ let cell_guard = smoothstep(0.025, 0.07, stones.edge);
4849
+ let stone_mask = stone_shape * cell_guard;
4850
+ let moss = levels(noise.fbm(uv * vec2(5.0, 5.0) + warp * 1.4, octaves: 4, seed: 59, period: 5) + (1.0 - stone_mask) * 0.25, black: 0.44, white: 0.92, gamma: 0.85);
4851
+ let wet = contrast(noise.fbm(uv * 3.0 + warp, octaves: 3, seed: 61, period: 3), amount: 1.55, pivot: 0.48);
4852
+ let height = soil * 0.045 + moss * 0.035 + sat(1.0 - stones.dist * 2.1) * stone_mask * 0.10 - (1.0 - soil) * 0.025;
4853
+ let soil_color = ramp(soil, [0.0: color("#211912"), 0.45: color("#3b2b1d"), 1.0: color("#6c5538")]);
4854
+ let moss_color = ramp(moss, [0.0: color("#26351d"), 0.55: color("#43622d"), 1.0: color("#7f9b55")]);
4855
+ let stone_color = ramp(rand(stones.id, seed: 67), [0.0: color("#3e4039"), 0.5: color("#68675b"), 1.0: color("#918a78")]);
4856
+ out.color = mix(mix(soil_color, moss_color, moss * 0.82), stone_color, stone_mask * 0.75);
4857
+ out.normal = normal_from_height(height, strength: 0.95);
4858
+ out.orm = orm(occlusion: ao_from_height(height, radius: 2.0), roughness: mix(0.92, 0.58, wet), metallic: 0.0);
4859
+ #end
4860
+
4861
+ object desert_wall {
4862
+ model = "cube"
4863
+ material = "DesertWall"
4864
+ position = (0, -240, 160)
4865
+ size = (700, 40, 320)
4866
+ }
4867
+
4868
+ object forest_ground {
4869
+ model = "cube"
4870
+ material = "ForestGround"
4871
+ position = (0, 180, -20)
4872
+ size = (900, 520, 40)
4873
+ }
4576
4874
 
4577
4875
  </example_texture_dsl_desert_forest_demo>
4578
4876
 
4579
4877
 
4580
4878
  <example_texture_dsl_grunge_demo>
4581
- // Procedural texture DSL demo: scalar mask morphology for grunge and worn painted metal.
4582
-
4583
- #texture GrungeMask
4584
- tile_m = 1.5;
4585
- wrap = repeat;
4586
- let p = uv * vec2(8.0, 8.0);
4587
- let warp = noise.vec2(uv * vec2(3.0, 3.0), seed: 71, period: 3) * 0.35;
4588
- let cloudy = levels(noise.fbm(p + warp, octaves: 5, seed: 73, period: vec2(8.0, 8.0)), black: 0.24, white: 0.82, gamma: 1.18);
4589
- let pepper = levels(noise.ridged(uv * vec2(72.0, 72.0), octaves: 3, seed: 79, period: 72), black: 0.58, white: 0.94, gamma: 0.75);
4590
- let streak = levels(noise.ridged(vec2(uv.x * 160.0, uv.y * 5.0), octaves: 2, seed: 83, period: vec2(160.0, 5.0)), black: 0.66, white: 0.93, gamma: 0.65);
4591
- let pits = dilate(erode(sat(cloudy * 0.62 + pepper * 0.32 + streak * 0.22), radius: 1.0), radius: 1.5);
4592
- let softened = blur(pits, radius: 1.25);
4593
- out.color = vec3(softened, softened, softened);
4594
- out.normal = normal_from_height(softened * 0.05, strength: 0.45);
4595
- out.orm = orm(occlusion: 1.0 - softened * 0.18, roughness: mix(0.55, 0.95, softened), metallic: 0.0);
4596
- #end
4597
-
4598
- #texture WornPaintedMetal
4599
- tile_m = 1.0;
4600
- wrap = repeat;
4601
- let p = uv * vec2(10.0, 10.0);
4602
- let warp = noise.vec2(uv * vec2(4.0, 4.0), seed: 89, period: 4) * 0.28;
4603
- let broad = noise.fbm(uv * vec2(5.0, 5.0) + warp, octaves: 4, seed: 97, period: 5);
4604
- let cells = pattern.voronoi(uv * vec2(14.0, 14.0) + warp * 0.5, jitter: 0.74, seed: 101, period: 14);
4605
- let crack = stroke(cells.edge, width: 0.035, aa: 0.010);
4606
- let scratch = levels(noise.ridged(vec2(uv.x * 180.0, uv.y * 7.0), octaves: 2, seed: 103, period: vec2(180.0, 7.0)), black: 0.68, white: 0.96, gamma: 0.72);
4607
- let chip_seed = levels(noise.ridged(p + warp, octaves: 4, seed: 107, period: 10), black: 0.62, white: 0.92, gamma: 0.85);
4608
- let raw_wear = sat(chip_seed * 0.70 + scratch * 0.32 + crack * 0.55 - broad * 0.18);
4609
- let wear = blur(dilate(erode(raw_wear, radius: 0.75), radius: 2.0), radius: 0.75);
4610
- let rust = levels(noise.fbm(uv * vec2(18.0, 18.0) + warp, octaves: 3, seed: 109, period: 18), black: 0.30, white: 0.90, gamma: 1.35);
4611
- let paint = color("#275f6f") * mix(0.70, 1.15, broad);
4612
- let exposed = mix(color("#6f7473"), color("#8c4a24"), rust * 0.55);
4613
- let height = broad * 0.035 - wear * 0.070 + scratch * 0.018;
4614
- out.color = mix(paint, exposed, wear);
4615
- out.normal = normal_from_height(height, strength: 0.95);
4616
- out.orm = orm(occlusion: ao_from_height(height, radius: 2.0), roughness: mix(0.62, 0.92, wear), metallic: mix(0.02, 0.70, wear * (1.0 - rust * 0.55)));
4617
- #end
4618
-
4619
- object grunge_panel {
4620
- model = "cube"
4621
- material = "GrungeMask"
4622
- position = (-260, 0, 80)
4623
- size = (260, 40, 180)
4624
- }
4625
-
4626
- object worn_painted_metal_panel {
4627
- model = "cube"
4628
- material = "WornPaintedMetal"
4629
- position = (180, 0, 80)
4630
- size = (360, 40, 220)
4631
- }
4879
+ // Procedural texture DSL demo: scalar mask morphology for grunge and worn painted metal.
4880
+
4881
+ #texture GrungeMask
4882
+ tile_m = 1.5;
4883
+ wrap = repeat;
4884
+ let p = uv * vec2(8.0, 8.0);
4885
+ let warp = noise.vec2(uv * vec2(3.0, 3.0), seed: 71, period: 3) * 0.35;
4886
+ let cloudy = levels(noise.fbm(p + warp, octaves: 5, seed: 73, period: vec2(8.0, 8.0)), black: 0.24, white: 0.82, gamma: 1.18);
4887
+ let pepper = levels(noise.ridged(uv * vec2(72.0, 72.0), octaves: 3, seed: 79, period: 72), black: 0.58, white: 0.94, gamma: 0.75);
4888
+ let streak = levels(noise.ridged(vec2(uv.x * 160.0, uv.y * 5.0), octaves: 2, seed: 83, period: vec2(160.0, 5.0)), black: 0.66, white: 0.93, gamma: 0.65);
4889
+ let pits = dilate(erode(sat(cloudy * 0.62 + pepper * 0.32 + streak * 0.22), radius: 1.0), radius: 1.5);
4890
+ let softened = blur(pits, radius: 1.25);
4891
+ out.color = vec3(softened, softened, softened);
4892
+ out.normal = normal_from_height(softened * 0.05, strength: 0.45);
4893
+ out.orm = orm(occlusion: 1.0 - softened * 0.18, roughness: mix(0.55, 0.95, softened), metallic: 0.0);
4894
+ #end
4895
+
4896
+ #texture WornPaintedMetal
4897
+ tile_m = 1.0;
4898
+ wrap = repeat;
4899
+ let p = uv * vec2(10.0, 10.0);
4900
+ let warp = noise.vec2(uv * vec2(4.0, 4.0), seed: 89, period: 4) * 0.28;
4901
+ let broad = noise.fbm(uv * vec2(5.0, 5.0) + warp, octaves: 4, seed: 97, period: 5);
4902
+ let cells = pattern.voronoi(uv * vec2(14.0, 14.0) + warp * 0.5, jitter: 0.74, seed: 101, period: 14);
4903
+ let crack = stroke(cells.edge, width: 0.035, aa: 0.010);
4904
+ let scratch = levels(noise.ridged(vec2(uv.x * 180.0, uv.y * 7.0), octaves: 2, seed: 103, period: vec2(180.0, 7.0)), black: 0.68, white: 0.96, gamma: 0.72);
4905
+ let chip_seed = levels(noise.ridged(p + warp, octaves: 4, seed: 107, period: 10), black: 0.62, white: 0.92, gamma: 0.85);
4906
+ let raw_wear = sat(chip_seed * 0.70 + scratch * 0.32 + crack * 0.55 - broad * 0.18);
4907
+ let wear = blur(dilate(erode(raw_wear, radius: 0.75), radius: 2.0), radius: 0.75);
4908
+ let rust = levels(noise.fbm(uv * vec2(18.0, 18.0) + warp, octaves: 3, seed: 109, period: 18), black: 0.30, white: 0.90, gamma: 1.35);
4909
+ let paint = color("#275f6f") * mix(0.70, 1.15, broad);
4910
+ let exposed = mix(color("#6f7473"), color("#8c4a24"), rust * 0.55);
4911
+ let height = broad * 0.035 - wear * 0.070 + scratch * 0.018;
4912
+ out.color = mix(paint, exposed, wear);
4913
+ out.normal = normal_from_height(height, strength: 0.95);
4914
+ out.orm = orm(occlusion: ao_from_height(height, radius: 2.0), roughness: mix(0.62, 0.92, wear), metallic: mix(0.02, 0.70, wear * (1.0 - rust * 0.55)));
4915
+ #end
4916
+
4917
+ object grunge_panel {
4918
+ model = "cube"
4919
+ material = "GrungeMask"
4920
+ position = (-260, 0, 80)
4921
+ size = (260, 40, 180)
4922
+ }
4923
+
4924
+ object worn_painted_metal_panel {
4925
+ model = "cube"
4926
+ material = "WornPaintedMetal"
4927
+ position = (180, 0, 80)
4928
+ size = (360, 40, 220)
4929
+ }
4632
4930
 
4633
4931
  </example_texture_dsl_grunge_demo>
4634
4932
 
4635
4933
 
4636
4934
  <example_tree_species>
4637
- // Curated #tree species gallery. Each recipe is a reference worth copying as a
4638
- // starting point for that habit; keep the shape keys, adjust seed/size per scene.
4639
- // Iterate look changes with preview_tree and watch the foliage_coverage stat.
4640
-
4641
- // European beech: broad asymmetric dome from four overlapping crown lobes, a
4642
- // trunk that persists partway into the crown (leader) then dissolves into forks,
4643
- // slender bole, dense cluster cards.
4644
- #tree Beech
4645
- seed: 27
4646
- mode: colonize
4647
- leader: 62
4648
- levels: 4
4649
- children: 7
4650
- segments: 7
4651
- trunk_height: 1250
4652
- trunk_radius: 34
4653
- points: 1024
4654
- length_pct: 76
4655
- radius_pct: 46
4656
- angle_deg: 46
4657
- angle_jitter_deg: 20
4658
- up_bias_pct: 26
4659
- curve_jitter_pct: 16
4660
- trunk_noise_pct: 4
4661
- trunk_lean_deg: 3 // gentle bands: beeches curve, they don't lean hard
4662
- trunk_sweep_pct: 16
4663
- trunk_noise_scale_pct: 30 // wavelength as % of height: slow bends, not bark-beat raggedness
4664
- leaves: 12
4665
- leaf_size: 178
4666
- droop_pct: 8
4667
- root_flare_pct: 34
4668
- crown_ellipsoid: 0, 0, 1020, 620, 590, 510
4669
- crown_ellipsoid: -250, 80, 960, 400, 370, 360
4670
- crown_ellipsoid: 240, -100, 1060, 420, 390, 400
4671
- crown_ellipsoid: 40, 50, 1380, 380, 350, 300
4672
- #end
4673
-
4674
- // Grand open-grown oak: a dominant central trunk visible deep into the tiered
4675
- // crown, three low limb masses ~120 degrees apart in plan (every viewing angle
4676
- // shows a reaching limb - lobes on one axis collapse to a blob from the thin
4677
- // side), offset upper lobes, real sky gaps. An irregular ellipsoid stack, not
4678
- // one sphere. Open crowns like this read ~35-45% coverage; gaps are the character.
4679
- #tree Oak
4680
- seed: 5
4681
- mode: colonize
4682
- leader: 35
4683
- levels: 4
4684
- children: 6
4685
- segments: 7
4686
- trunk_height: 950
4687
- trunk_radius: 44
4688
- points: 1024
4689
- length_pct: 80
4690
- radius_pct: 52
4691
- angle_deg: 55
4692
- angle_jitter_deg: 28
4693
- up_bias_pct: 22
4694
- curve_jitter_pct: 26
4695
- trunk_noise_pct: 7
4696
- trunk_lean_deg: 5 // low bands: a real oak is never a plumb pole
4697
- trunk_sweep_pct: 20 // one slow arc; seed picks C vs S
4698
- trunk_gnarl_pct: 40 // lobed lower-bole silhouette for hero close-ups (display-only)
4699
- leaves: 13
4700
- leaf_size: 190
4701
- droop_pct: 12
4702
- root_flare_pct: 45
4703
- crown_ellipsoid: 0, 0, 1120, 700, 660, 560
4704
- crown_ellipsoid: 435, 115, 930, 480, 430, 340
4705
- crown_ellipsoid: -320, 320, 900, 470, 420, 330
4706
- crown_ellipsoid: -115, -435, 1000, 450, 410, 350
4707
- crown_ellipsoid: -200, -190, 1500, 450, 410, 340
4708
- crown_ellipsoid: 230, 180, 1620, 390, 360, 300
4709
- #end
4710
-
4711
- // Fir: the dense layered conifer - whorl rings of downswept shelf branches
4712
- // (angle_deg > 90 + up_bias swoop) rendered as whole-branch frond ribbons,
4713
- // clear bole below crown_base, trunk-only collider. crown_base ~120 instead
4714
- // gives the ground-sweeping park look.
4715
- #tree Fir
4716
- seed: 11
4717
- mode: whorl
4718
- leaf_mode: frond
4719
- levels: 2
4720
- children: 12
4721
- rings: 30
4722
- fill: 10
4723
- segments: 8
4724
- trunk_height: 1400
4725
- trunk_radius: 22
4726
- trunk_noise_pct: 2
4727
- trunk_lean_deg: 1 // conifer spires stand near-plumb; kill the default lean/sweep
4728
- trunk_sweep_pct: 3
4729
- curve_jitter_pct: 5
4730
- angle_deg: 106
4731
- up_bias_pct: 8
4732
- length_pct: 18
4733
- length_taper_pct: 70
4734
- droop_pct: 34
4735
- leaf_size: 200
4736
- root_flare_pct: 25
4737
- crown_base: 300
4738
- collider_levels: 1
4739
- #end
4740
-
4741
- // Weeping willow: spiral limbs arch up (up_bias) then cascade (droop) carrying
4742
- // gravity-hung strand curtains (leaf_mode weeping). Long falls come from
4743
- // leaf_size (the curtain card side, cm) - ~300 reaches near the ground from a
4744
- // stout mid-height fork.
4745
- #tree Willow
4746
- seed: 9
4747
- mode: spiral
4748
- leaf_mode: weeping
4749
- levels: 3
4750
- children: 10
4751
- segments: 8
4752
- trunk_height: 750
4753
- trunk_radius: 46
4754
- length_pct: 82
4755
- radius_pct: 50
4756
- angle_deg: 40
4757
- angle_jitter_deg: 18
4758
- up_bias_pct: 34
4759
- curve_jitter_pct: 18
4760
- trunk_noise_pct: 5
4761
- trunk_lean_deg: 4
4762
- trunk_sweep_pct: 25 // waterside willows arc; pairs with the cascading crown
4763
- droop_pct: 85
4764
- leaves: 20
4765
- leaf_size: 300
4766
- root_flare_pct: 48
4767
- #end
4768
-
4769
- // Umbrella-thorn acacia: crown_dome mushroom cap on a bare fanning scaffold. The
4770
- // short trunk + high cap forces colonize to bridge with long limbs; the TWO bare
4771
- // guide ellipsoids make them diverge early and fork again mid-way (a lone
4772
- // overhead cap pulls one straight vertical reach instead - attraction averages
4773
- // out). children raises the forks per node. leaf_base at the dome cut with a
4774
- // short fade keeps every guide bare and the cap underside crisp and flat.
4775
- #tree Acacia
4776
- seed: 4
4777
- mode: colonize
4778
- children: 7
4779
- trunk_height: 260
4780
- trunk_radius: 36
4781
- points: 1024
4782
- length_pct: 80
4783
- radius_pct: 60
4784
- curve_jitter_pct: 18
4785
- trunk_noise_pct: 6
4786
- trunk_lean_deg: 8 // savanna wind-shaped bole; crown_follow keeps the cap on it
4787
- leaves: 13
4788
- leaf_size: 185
4789
- leaf_base: 740
4790
- leaf_base_fade: 60
4791
- root_flare_pct: 35
4792
- crown_ellipsoid: 0, 0, 520, 300, 300, 160
4793
- crown_ellipsoid: 0, 0, 690, 460, 460, 90
4794
- crown_dome: 0, 0, 750, 800, 800, 360
4795
- #end
4796
-
4797
- object Beech1 { model = "tree.Beech" position = (0, 0, 0) }
4798
- object Oak1 { model = "tree.Oak" position = (2200, 0, 0) }
4799
- object Fir1 { model = "tree.Fir" position = (4000, 0, 0) }
4800
- object Willow1 { model = "tree.Willow" position = (5800, 0, 0) }
4801
- object Acacia1 { model = "tree.Acacia" position = (8000, 0, 0) }
4935
+ // Curated #tree species gallery. Each recipe is a reference worth copying as a
4936
+ // starting point for that habit; keep the shape keys, adjust seed/size per scene.
4937
+ // Iterate look changes with preview_tree and watch the foliage_coverage stat.
4938
+
4939
+ // European beech: broad asymmetric dome from four overlapping crown lobes, a
4940
+ // trunk that persists partway into the crown (leader) then dissolves into forks,
4941
+ // slender bole, dense cluster cards.
4942
+ #tree Beech
4943
+ seed: 27
4944
+ mode: colonize
4945
+ leader: 62
4946
+ levels: 4
4947
+ children: 7
4948
+ segments: 7
4949
+ trunk_height: 1250
4950
+ trunk_radius: 34
4951
+ points: 1024
4952
+ length_pct: 76
4953
+ radius_pct: 46
4954
+ angle_deg: 46
4955
+ angle_jitter_deg: 20
4956
+ up_bias_pct: 26
4957
+ curve_jitter_pct: 16
4958
+ trunk_noise_pct: 4
4959
+ trunk_lean_deg: 3 // gentle bands: beeches curve, they don't lean hard
4960
+ trunk_sweep_pct: 16
4961
+ trunk_noise_scale_pct: 30 // wavelength as % of height: slow bends, not bark-beat raggedness
4962
+ leaves: 12
4963
+ leaf_size: 178
4964
+ droop_pct: 8
4965
+ root_flare_pct: 34
4966
+ crown_ellipsoid: 0, 0, 1020, 620, 590, 510
4967
+ crown_ellipsoid: -250, 80, 960, 400, 370, 360
4968
+ crown_ellipsoid: 240, -100, 1060, 420, 390, 400
4969
+ crown_ellipsoid: 40, 50, 1380, 380, 350, 300
4970
+ #end
4971
+
4972
+ // Grand open-grown oak: a dominant central trunk visible deep into the tiered
4973
+ // crown, three low limb masses ~120 degrees apart in plan (every viewing angle
4974
+ // shows a reaching limb - lobes on one axis collapse to a blob from the thin
4975
+ // side), offset upper lobes, real sky gaps. An irregular ellipsoid stack, not
4976
+ // one sphere. Open crowns like this read ~35-45% coverage; gaps are the character.
4977
+ #tree Oak
4978
+ seed: 5
4979
+ mode: colonize
4980
+ leader: 35
4981
+ levels: 4
4982
+ children: 6
4983
+ segments: 7
4984
+ trunk_height: 950
4985
+ trunk_radius: 44
4986
+ points: 1024
4987
+ length_pct: 80
4988
+ radius_pct: 52
4989
+ angle_deg: 55
4990
+ angle_jitter_deg: 28
4991
+ up_bias_pct: 22
4992
+ curve_jitter_pct: 26
4993
+ trunk_noise_pct: 7
4994
+ trunk_lean_deg: 5 // low bands: a real oak is never a plumb pole
4995
+ trunk_sweep_pct: 20 // one slow arc; seed picks C vs S
4996
+ trunk_gnarl_pct: 40 // lobed lower-bole silhouette for hero close-ups (display-only)
4997
+ leaves: 13
4998
+ leaf_size: 190
4999
+ droop_pct: 12
5000
+ root_flare_pct: 45
5001
+ crown_ellipsoid: 0, 0, 1120, 700, 660, 560
5002
+ crown_ellipsoid: 435, 115, 930, 480, 430, 340
5003
+ crown_ellipsoid: -320, 320, 900, 470, 420, 330
5004
+ crown_ellipsoid: -115, -435, 1000, 450, 410, 350
5005
+ crown_ellipsoid: -200, -190, 1500, 450, 410, 340
5006
+ crown_ellipsoid: 230, 180, 1620, 390, 360, 300
5007
+ #end
5008
+
5009
+ // Fir: the dense layered conifer - whorl rings of downswept shelf branches
5010
+ // (angle_deg > 90 + up_bias swoop) rendered as whole-branch frond ribbons,
5011
+ // clear bole below crown_base, trunk-only collider. crown_base ~120 instead
5012
+ // gives the ground-sweeping park look.
5013
+ #tree Fir
5014
+ seed: 11
5015
+ mode: whorl
5016
+ leaf_mode: frond
5017
+ levels: 2
5018
+ children: 12
5019
+ rings: 30
5020
+ fill: 10
5021
+ segments: 8
5022
+ trunk_height: 1400
5023
+ trunk_radius: 22
5024
+ trunk_noise_pct: 2
5025
+ trunk_lean_deg: 1 // conifer spires stand near-plumb; kill the default lean/sweep
5026
+ trunk_sweep_pct: 3
5027
+ curve_jitter_pct: 5
5028
+ angle_deg: 106
5029
+ up_bias_pct: 8
5030
+ length_pct: 18
5031
+ length_taper_pct: 70
5032
+ droop_pct: 34
5033
+ leaf_size: 200
5034
+ root_flare_pct: 25
5035
+ crown_base: 300
5036
+ collider_levels: 1
5037
+ #end
5038
+
5039
+ // Weeping willow: spiral limbs arch up (up_bias) then cascade (droop) carrying
5040
+ // gravity-hung strand curtains (leaf_mode weeping). Long falls come from
5041
+ // leaf_size (the curtain card side, cm) - ~300 reaches near the ground from a
5042
+ // stout mid-height fork.
5043
+ #tree Willow
5044
+ seed: 9
5045
+ mode: spiral
5046
+ leaf_mode: weeping
5047
+ levels: 3
5048
+ children: 10
5049
+ segments: 8
5050
+ trunk_height: 750
5051
+ trunk_radius: 46
5052
+ length_pct: 82
5053
+ radius_pct: 50
5054
+ angle_deg: 40
5055
+ angle_jitter_deg: 18
5056
+ up_bias_pct: 34
5057
+ curve_jitter_pct: 18
5058
+ trunk_noise_pct: 5
5059
+ trunk_lean_deg: 4
5060
+ trunk_sweep_pct: 25 // waterside willows arc; pairs with the cascading crown
5061
+ droop_pct: 85
5062
+ leaves: 20
5063
+ leaf_size: 300
5064
+ root_flare_pct: 48
5065
+ #end
5066
+
5067
+ // Umbrella-thorn acacia: crown_dome mushroom cap on a bare fanning scaffold. The
5068
+ // short trunk + high cap forces colonize to bridge with long limbs; the TWO bare
5069
+ // guide ellipsoids make them diverge early and fork again mid-way (a lone
5070
+ // overhead cap pulls one straight vertical reach instead - attraction averages
5071
+ // out). children raises the forks per node. leaf_base at the dome cut with a
5072
+ // short fade keeps every guide bare and the cap underside crisp and flat.
5073
+ #tree Acacia
5074
+ seed: 4
5075
+ mode: colonize
5076
+ children: 7
5077
+ trunk_height: 260
5078
+ trunk_radius: 36
5079
+ points: 1024
5080
+ length_pct: 80
5081
+ radius_pct: 60
5082
+ curve_jitter_pct: 18
5083
+ trunk_noise_pct: 6
5084
+ trunk_lean_deg: 8 // savanna wind-shaped bole; crown_follow keeps the cap on it
5085
+ leaves: 13
5086
+ leaf_size: 185
5087
+ leaf_base: 740
5088
+ leaf_base_fade: 60
5089
+ root_flare_pct: 35
5090
+ crown_ellipsoid: 0, 0, 520, 300, 300, 160
5091
+ crown_ellipsoid: 0, 0, 690, 460, 460, 90
5092
+ crown_dome: 0, 0, 750, 800, 800, 360
5093
+ #end
5094
+
5095
+ object Beech1 { model = "tree.Beech" position = (0, 0, 0) }
5096
+ object Oak1 { model = "tree.Oak" position = (2200, 0, 0) }
5097
+ object Fir1 { model = "tree.Fir" position = (4000, 0, 0) }
5098
+ object Willow1 { model = "tree.Willow" position = (5800, 0, 0) }
5099
+ object Acacia1 { model = "tree.Acacia" position = (8000, 0, 0) }
4802
5100
 
4803
5101
  </example_tree_species>
4804
5102
 
@@ -5272,19 +5570,23 @@ screenCanvas TutorialUI {
5272
5570
 
5273
5571
 
5274
5572
  <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
5573
+ // Drivable car with an NPC driver you can carjack - the seat primitive end-to-end on a MODEL
5574
+ // car. SetModelAnchor mints the "driver" seat once per model (model-root local cm, Z-up,
5575
+ // +X = nose; hips land there, rider faces +X); SeatPlayer parks riders on it: the engine
5576
+ // suspends the capsule + native controls, the avatar rides the seat, the default chase
5278
5577
  // camera follows - no per-frame camera or position writes. Car feel (accel/drag/turn)
5279
5578
  // stays script-owned kinematic velocity integration; seat roles and jack rules are
5280
- // script vars. Meshed vehicles: author a socket("driver", ...) and use SeatPlayer.
5579
+ // script vars. Primitive-built seats (a cushion block, no model): SeatPlayerAt(P, "driver", offset).
5281
5580
 
5282
5581
  #class CarController
5283
5582
  var Vel = Vector(0, 0, 0)
5284
5583
  var TopSpeed = 900
5285
- var SeatOffset = Vector(0, 0, 55) // the car owns its seat geometry - never duplicate it per caller
5584
+ event OnSpawned {
5585
+ // The sedan model is center-origin and 140 tall: hips ~50cm above the underbody -> z -20; driver side +Y.
5586
+ SetModelAnchor(GetModel(), "driver", Position(-5, 40, -20), Vector(0, 0, 0))
5587
+ }
5286
5588
  func Board(P: Object) {
5287
- SeatPlayerAt(P, "driver", SeatOffset)
5589
+ SeatPlayer(P, "driver")
5288
5590
  }
5289
5591
  event Update {
5290
5592
  var driver = SeatOccupant("driver")
@@ -5331,7 +5633,7 @@ event ActionDownByThisPlayer[Interact] {
5331
5633
 
5332
5634
  material Asphalt { base = "PolishedConcrete" }
5333
5635
  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) }
5636
+ object Taxi : CarController { model = "alias.model.sedan" height = 140 position = (300, 0, 5) color = (0.95, 0.75, 0.1) }
5335
5637
  display manager : CityManager { visible = false }
5336
5638
  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
5639
 
@@ -5359,15 +5661,18 @@ Worlds may carry `#doc_version N` = the doc version they were last authored/upgr
5359
5661
  - only use '//' for comments
5360
5662
  - Destroy() also ends the current function body (a value-returning func returns its type default); the caller keeps running this frame.
5361
5663
  - Object member calls (including Destroy()) on a None or already-despawned Object are runtime errors (a second Destroy() before the queued despawn runs is fine). Avoid spamming Exists(); prefer fail-fast. Use Exists() only when absence is expected (mouse/crosshair miss, destroyed mid-loop). Note: Object.Exists() is safe on None (returns false), so `o == None or not o.Exists()` is redundant; write `not o.Exists()`.
5664
+ - `and`/`or` evaluate left to right and short-circuit: `o.Exists() and o.Hp > 0` is a safe guard. IsConnected() is a member call, not a presence check - it throws on a stale handle, guard it with Exists(). An Object local can despawn during Wait(): re-test Exists() after every Wait().
5362
5665
  - Reference a named scene object with Scene.X (e.g. Scene.Hotbar, Scene.EnemyBase.SetVisible(true)). It resolves once at world load; Exists() is false if no such object. Runtime-spawned objects are not reachable this way - keep the SpawnObject result in a var.
5363
5666
  - Never write saved_origin inside a saved clause (wrong: saved BrickBlock { object BrickBlock { saved_origin = "BrickBlock" } })
5364
5667
  - when asked to make a game similar to original, stay as close to the original as possible in terms of game mechanics
5365
5668
  - Split subsystem logic (production, AI opponent, camera, selection UI) into small manager classes early; don't let one class accumulate everything.
5366
5669
  - Use one List<T> instead of numbered var runs (Slot0, Slot1, Slot2, ...).
5367
5670
  - Objects are engine-pooled: SpawnObject/Destroy reuses object slots and is cheap. Spawn from `saved` templates; never build manual pools of pre-placed hidden objects.
5368
- - SetAssetSubmesh* calls are global per-asset: call each once per scene (e.g. OnPrespawn of a single setup object), not per spawned object.
5671
+ - SetAssetSubmesh* calls are global per-asset: call each once per scene (e.g. OnPreSpawn of a single setup object), not per spawned object.
5369
5672
  - If scene init manipulates many static objects (OnSpawned chains, setup before the main loop), call RefreshStaticBaseline() once at the end of init.
5370
5673
  - Delete debug scaffolding (test cursors, debug spawn panels, temp markers) once the feature works.
5674
+ - Class funcs cannot take an engine simcall name (Die, Attack, Jump, ...): compile error W2C0623 - rename (e.g. PlayerDeath).
5675
+ - A triple-quoted `text = """..."""` value must be the last token on its line; put other properties on their own lines.
5371
5676
  </additional_notes>
5372
5677
 
5373
5678
  <weave2_migration>