← Dev Log
BallOrcs3 min read

Two levels, three abilities, and a wardrobe of faces

A week ago BallOrcs was a few test steps in an empty room.

In plain words

A week ago BallOrcs was a few test steps in an empty room. Now there are two real levels you can play start to finish: a village that teaches you to move, jump, collect and dash, and a gloomy fire territory where you burn your way through blocked walls until the Fount reignites and the colour comes back. Your orc can dash, flame-dash, and glide. There are checkpoints, a first enemy that flattens you on contact (or squashes if you land on it), a results screen, a level-select hub, and a save file that remembers all of it. We also finished the wardrobe — eyes, mouths, hats, accessories, noses, skins and feet — and taught the game to play on a phone.

One of the new full-body skins: cracked magma

Under the hood

Sixteen backlog issues landed. The shape of the week:

  • Levels. level1.tscn (#24) replaced main.tscn as the main scene — move → jump → collect → speed, in order, ending at the Fount. Its speed crossing is a genuine required gate (a real gap, flanked by full-length boundary walls) so the goal bot's detour logic can't route around it. level2.tscn (#30) is the Fire Fount route: two full-corridor Burnable gates, an oil-slick hazard, an optional speed-pad detour to bonus orbs. Both got art/lighting passes (#29, #33) — L2's level2_atmosphere.gd starts desaturated and warms to full ember vibrance in two steps off Burnable.ignited, so the Flattening's grey literally lifts as you burn.
  • Three abilities, all GDScript. Ability::activate() only dispatches to script overrides, so new ability ids stay additive with no GDExtension rebuild. Speed (#19) is a burst — with cooldown_duration explicitly raised to 3.0 because a 1.5s burst on a 1.0s default cooldown could re-trigger before the ball ever dropped back to base speed. Fire (#20) is a dash that ignites anything exposing an ignite() method (duck-typed seam; #22 shipped the other half independently). Flight (#21) is hold-to-glide, driven from _do_tick rather than _do_activate — AbilityHolder only calls activate() on is_action_just_pressed, a discrete edge, which is wrong for a hold.
  • A world that reacts. Burnable, UpdraftFan and BoostGate (#22), fire hazards — torch chain, flame geyser (#31) — the wind family (#36: wind_current.gd lateral push, wind_gust.gd telegraphed burst), EnemyV1 (#32, patrol + contact damage + Goomba-style stomp), checkpoints, kill volumes and orb pickups. Every gate has a no-button-press path (igniter_torch.gd, speed_pad.gd) because the goal bot can never hold ability.
  • Everything around the game. Main menu with a shared options panel, contextual tutorial prompts with input-glyph substitution (#25), level completion + results screen (#26), a versioned forward-compatible SaveProfile (#40) where unknown fields survive a load→save round-trip and a corrupt user://profile.json falls back to defaults with a warning, and a level-select hub (#41) whose locked rows are non-focusable so pad/keyboard tab order skips them for free.
  • Phones. #50 added a virtual drag joystick feeding Input.action_press(action, strength) — the same actions the bot injects, so the harness stays device-agnostic — plus TouchScreenButton nodes for jump/ability. Positions honour DisplayServer.get_display_safe_area(); the whole thing is gated off under --playtest and hidden on non-touch devices. project.godot gained canvas_items stretch at 1280x720 so the UI scales instead of going microscopic at high DPI. Not verified on a real device — no Android SDK in this sandbox.
  • The wardrobe finished. Skin (4 full-body material variants — matte, glossy, cracked-magma, bubbling), nose (3), feet (3) and a starter world-prop vocabulary (torch, rock cluster, platform block). Feet are the first slot tied to gameplay: toggle_stance (C) flips between rolling and a bipedal presentation where the body floats above two independent feet — collision and movement math stay byte-identical, only mesh offset, foot visibility and a step bob change.
  • Honest gaps. No barber-shop UI yet, so the components can't be swapped in-game. The skins are same-radius opaque spheres that will z-fight with ball_mesh until a real swap replaces it rather than instancing alongside.
#gameplay#levels#abilities#characters#mobile