[Week 5] Asteroid Loot, Docking, NPCs

4 minute read Published: 2026-04-19

[Mon, 13 Apr 2026]

Asteroid explosions and collisions turned out solid. The pipeline components are in place for sprite sheets, volumetric smoke, sparks, embers and debris.

Cella Asteroid vs Ship

Still lots of room for improvement on the visuals, and some minor issues on client-side projectile collision rendering. I'm hoping to wrap that up today and move on to loot - the idea being that destroying large asteroids provides a chance for receiving cells for use in construction.


[Wed, 15 Apr 2026]

Yesterday was lost fighting with MSAA, shadows and performance concerns at proximity to planets.

I'm happy with the visual pipeline. The effects still need a lot of work, and loot collection mechanics need reconsideration.

NPC foundation next. The first question I need to settle is whether to implement in the existing Lua API (logic cells) or directly in Rust.


[Thu, 16 Apr 2026]

Lots of API progress today. A working orbital droid with avoidance:

Orbital w/ Avoidance Code (Lua)
-- Orbit geometry
local ORBIT_RADIUS = 0.425
local ORBIT_SPEED  = 0.075
local ORBIT_NORMAL = { x = 0.707, y = 0.707, z = 0.0 }
local BANK_ANGLE   = -0.32

-- Collision avoidance
local AVOIDANCE_LOOKAHEAD_SEC = 8.0
local AVOIDANCE_SAFETY_RADIUS = 0.05
local AVOIDANCE_MAX_ACCEL     = 0.5
local AVOIDANCE_MIN_OFFSET    = 0.001
local AVOIDANCE_MIN_VREL_SQ   = 1e-8
local AVOIDANCE_ENGAGE_THRESHOLD = 1e-6


function tick()
    if not ship.captured then
        nav.orbit = nil
        return
    end

    -- Accumulate avoidance push from sensor contacts.
    local ax, ay, az = 0.0, 0.0, 0.0
    for i = 1, sensors.count do
        local c = sensors.contacts[i]
        if c.id ~= ship.anchor_id then
            local px, py, pz = c.position.x, c.position.y, c.position.z
            local vrx = c.velocity.x - ship.velocity.x
            local vry = c.velocity.y - ship.velocity.y
            local vrz = c.velocity.z - ship.velocity.z
            local vrel_sq = vrx * vrx + vry * vry + vrz * vrz
            if vrel_sq > AVOIDANCE_MIN_VREL_SQ then
                local p_dot_vrel = px * vrx + py * vry + pz * vrz
                local t_ca = -p_dot_vrel / vrel_sq
                if t_ca > 0.0 and t_ca < AVOIDANCE_LOOKAHEAD_SEC then
                    local ca_x = px + vrx * t_ca
                    local ca_y = py + vry * t_ca
                    local ca_z = pz + vrz * t_ca
                    local miss = math.sqrt(ca_x * ca_x + ca_y * ca_y + ca_z * ca_z)
                    if miss < AVOIDANCE_SAFETY_RADIUS
                        and miss > AVOIDANCE_MIN_OFFSET then
                        -- Urgency by miss distance only
                        local urgency = 1.0 - miss / AVOIDANCE_SAFETY_RADIUS
                        local push_mag = urgency * AVOIDANCE_MAX_ACCEL
                        local inv = -push_mag / miss
                        ax = ax + ca_x * inv
                        ay = ay + ca_y * inv
                        az = az + ca_z * inv
                    end
                end
            end
        end
    end

    local avoid_mag = math.sqrt(ax * ax + ay * ay + az * az)
    if avoid_mag > AVOIDANCE_ENGAGE_THRESHOLD then
        -- Active avoidance, suppress orbit and take manual control
        nav.orbit = nil
        nav.turn_toward = {
            x = ship.position.x + ax / avoid_mag,
            y = ship.position.y + ay / avoid_mag,
            z = ship.position.z + az / avoid_mag,
        }
        nav.thrust = math.min(1.0, avoid_mag)
    else
        -- Path clear, hand off to Rust orbit primitive
        nav.orbit = {
            target = ship.anchor_id,
            radius = ORBIT_RADIUS,
            speed  = ORBIT_SPEED,
            normal = ORBIT_NORMAL,
            bank_angle = BANK_ANGLE,
        }
    end
end

Some additional primitives will be needed to build a living universe. The work also exposed lots of gaps in energy management, cell destruction and fragmentation.


[Sat, 18 Apr 2026]

Those additional primitives that I last spoke have taken the last two days of effort to accommodate. Before, with 1x1x1 cubes, the collision pass could treat cells as small spheres which made everything simple. Supporting 1x1x3 and 10x10x1 entails significant upgrades to the physics engine, debris and re-thinking the editor's cursor-based approach.

Asteroid loot is working. Simple NPCs are working. Now it's time to build a simple but lively environment. Docking is next.


[Sun, 19 Apr 2026]

Docking works nicely; any changes I'd want to make from here would be stylistic. Eventually docking will invoke a 2D interface for services (which will also be cells).

As I test out different structures (mostly AI-generated, admittedly) I'm realizing that scale of components may be an issue. For one, having structures that are 95% composed of components serving no use than structure isn't ideal. For another doing so makes it really difficult to target individual cells. So, something fundamental needs to change.

It may be simply a matter of exaggerating the size of functional components versus structural components; so that's something I'll likely try. Another consideration are structural frames - the idea that ships and structures need to begin with a frame or foundation of sorts. Both could work I suppose.

Those ideas coupled with a lot of plans on structures and vessels I'm hoping to place in the living system should narrow the challenge down.


Follow the journey...

⬅ Last Week Home


Get dev updates on Cella. No spam, unsubscribe anytime.