Compiler not telling me where the code is wrong?

Trying to compile a vibe coded odin project I get this output:

/a/proj/zarch ❯ odin run zarch
/usr/lib/odin/core/math/math.odin(391:85) Error: Mismatched types in binary expression 'a * (1 - t)' : 'f32' vs 'f64' 
        ... ontextless" (a, b: $T, t: $E) -> (x: T) { return a*(1-t) + b*t } 
                                                              ^ 
 
/usr/lib/odin/core/math/math.odin(391:95) Error: Mismatched types in binary expression 'b * t' : 'f32' vs 'f64' 
        ... ontextless" (a, b: $T, t: $E) -> (x: T) { return a*(1-t) + b*t } 
                                                                        ^

How am I supposed to know where I (well, eh gemini) did wrong? It points to somewhere in the stdlib but no indication where in my code there is a problem?

Sorry for being a noob. :slight_smile:
– Henrik

zarch.odin in zarch folder (warning gemini 3 pro output):

package zarch

import "core:fmt"
import "core:math"
import "core:math/linalg"
import "core:math/rand"
import rl "vendor:raylib"

// --- Constants ---
SCREEN_WIDTH   :: 800
SCREEN_HEIGHT  :: 600
MAP_SIZE       :: 64
TILE_SIZE      :: 2.0
GRAVITY        :: 9.8
THRUST_POWER   :: 25.0
DRAG_FACTOR    :: 0.5
MAX_PARTICLES  :: 2000
MAX_ENEMIES    :: 10

// --- Structs ---
Particle :: struct {
    active: bool,
    position: rl.Vector3,
    velocity: rl.Vector3,
    color:    rl.Color,
    life:     f32, // Remaining time
    size:     f32,
}

Enemy :: struct {
    active:   bool,
    position: rl.Vector3,
    velocity: rl.Vector3,
    offset_t: f32, // Time offset for hovering animation
}

Player :: struct {
    position:     rl.Vector3,
    velocity:     rl.Vector3,
    yaw:          f32,
    pitch:        f32,
    is_thrusting: bool,
}

Game_State :: struct {
    player:     Player,
    camera:     rl.Camera3D,
    height_map: [MAP_SIZE][MAP_SIZE]f32,
    
    // Systems
    particles:  [MAX_PARTICLES]Particle,
    p_index:    int, // Ring buffer index
    enemies:    [MAX_ENEMIES]Enemy,
}

// --- Math Helpers ---
get_forward_vector :: proc(yaw, pitch: f32) -> rl.Vector3 {
    cx := math.cos(pitch)
    sx := math.sin(pitch)
    cy := math.cos(yaw)
    sy := math.sin(yaw)
    return rl.Vector3{sx * sy, cx, sx * cy}
}

get_terrain_height :: proc(state: ^Game_State, x, z: f32) -> f32 {
    map_offset := f32(MAP_SIZE * TILE_SIZE) / 2.0
    gx := int((x + map_offset) / TILE_SIZE)
    gz := int((z + map_offset) / TILE_SIZE)

    if gx >= 0 && gx < MAP_SIZE && gz >= 0 && gz < MAP_SIZE {
        return state.height_map[gx][gz]
    }
    return -50.0 // Abyss
}

// --- Particle System ---
spawn_particle :: proc(state: ^Game_State, pos, vel: rl.Vector3, col: rl.Color, size, life: f32) {
    idx := state.p_index
    state.particles[idx] = Particle{
        active   = true,
        position = pos,
        velocity = vel,
        color    = col,
        life     = life,
        size     = size,
    }
    state.p_index = (state.p_index + 1) % MAX_PARTICLES
}

update_particles :: proc(state: ^Game_State, dt: f32) {
    for i in 0..<MAX_PARTICLES {
        p := &state.particles[i]
        if !p.active do continue

        p.life -= dt
        if p.life <= 0 {
            p.active = false
            continue
        }

        p.position += p.velocity * dt
        
        // Simple floor collision for particles
        h := get_terrain_height(state, p.position.x, p.position.z)
        if p.position.y < h {
            p.position.y = h
            p.velocity.y = 0
            p.velocity.x *= 0.5 // Friction
            p.velocity.z *= 0.5
        }
    }
}

// --- Enemy System ---
init_enemies :: proc(state: ^Game_State) {
    for i in 0..<MAX_ENEMIES {
        // Random spawn
        rx := (rand.float32() * 80.0) - 40.0
        rz := (rand.float32() * 80.0) - 40.0
        state.enemies[i] = Enemy{
            active   = true,
            position = rl.Vector3{rx, 30.0, rz},
            offset_t = rand.float32() * 10.0,
        }
    }
}

update_enemies :: proc(state: ^Game_State, dt: f32) {
    for i in 0..<MAX_ENEMIES {
        e := &state.enemies[i]
        if !e.active do continue

        // 1. Behavior: Hover and slowly chase player logic
        target := state.player.position
        diff := target - e.position
        dist := math.sqrt(diff.x*diff.x + diff.z*diff.z)

        // Move towards player slowly
        dir := rl.Vector3Normalize(diff)
        speed : f32 = 4.0
        
        // Add some sine wave bobbing
        e.offset_t += dt
        bob := math.sin(e.offset_t * 2.0) * 0.1

        e.velocity = rl.Vector3{dir.x * speed, bob, dir.z * speed}
        e.position += e.velocity * dt

        // 2. Terrain Constraint (Keep them above ground)
        h := get_terrain_height(state, e.position.x, e.position.z)
        desired_alt := h + 10.0 + (math.sin(e.offset_t)*2.0)
        
        // Smoothly adjust altitude
        e.position.y = math.lerp(e.position.y, desired_alt, 0.05)

        // 3. Emit "Virus" Particles (Green trail)
        if rand.float32() > 0.8 {
            spawn_particle(state, 
                e.position, 
                rl.Vector3{(rand.float32()-0.5), (rand.float32()-0.5), (rand.float32()-0.5)}, 
                rl.LIME, 0.4, 1.0)
        }
    }
}

// --- Main Logic ---

init_terrain :: proc(state: ^Game_State) {
    for x in 0..<MAP_SIZE {
        for z in 0..<MAP_SIZE {
            fx := f32(x) * 0.15
            fz := f32(z) * 0.15
            height := math.sin(fx) * 3.0 + math.cos(fz) * 3.0 + math.sin(fx + fz) * 1.5
            state.height_map[x][z] = height - 10.0 
        }
    }
}

update_player :: proc(state: ^Game_State, dt: f32) {
    p := &state.player
    
    // Input
    mouse_delta := rl.GetMouseDelta()
    p.yaw   -= mouse_delta.x * 0.005
    p.pitch -= mouse_delta.y * 0.005
    p.pitch = math.clamp(p.pitch, -math.PI + 0.1, -0.1) 

    p.is_thrusting = rl.IsMouseButtonDown(.LEFT) || rl.IsKeyDown(.W) || rl.IsKeyDown(.SPACE)
    forward := get_forward_vector(p.yaw, p.pitch)

    // Physics
    accel := rl.Vector3{0, -GRAVITY, 0}
    if p.is_thrusting {
        accel += forward * THRUST_POWER
        
        // PARTICLES: Engine Exhaust
        // Spawn particle behind the ship
        exhaust_pos := p.position - (forward * 0.5)
        // Add some randomness to velocity
        rnd_vel := rl.Vector3{
            (rand.float32() - 0.5) * 5.0,
            (rand.float32() - 0.5) * 5.0,
            (rand.float32() - 0.5) * 5.0,
        }
        spawn_particle(state, exhaust_pos, (-forward * 10.0) + rnd_vel, rl.ORANGE, 0.5, 0.5)
    }

    accel -= p.velocity * DRAG_FACTOR
    p.velocity += accel * dt
    p.position += p.velocity * dt

    // Terrain Collision & Dust
    h := get_terrain_height(state, p.position.x, p.position.z)
    
    // Dust effect when close to ground
    dist_to_ground := p.position.y - h
    if dist_to_ground < 8.0 && dist_to_ground > 0.0 {
        // More dust the closer we are
        chance := 1.0 - (dist_to_ground / 8.0)
        if rand.float32() < chance {
            // Spawn dust at ground level
            dust_pos := rl.Vector3{
                p.position.x + (rand.float32()*4.0 - 2.0),
                h + 0.5,
                p.position.z + (rand.float32()*4.0 - 2.0),
            }
            spawn_particle(state, dust_pos, rl.Vector3{0, 1.0, 0}, rl.BROWN, 0.8, 1.5)
        }
    }

    if p.position.y < h + 0.5 {
        p.position.y = h + 0.5
        p.velocity.y *= -0.5
        p.velocity.x *= 0.8
        p.velocity.z *= 0.8
    }
}

main :: proc() {
    rl.InitWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "Odin Zarch: Particles & Enemies")
    defer rl.CloseWindow()
    rl.SetTargetFPS(60)
    rl.DisableCursor()

    state := Game_State{}
    state.player.position = rl.Vector3{0, 10, 0}
    state.player.pitch = -math.PI / 2.0 
    
    state.camera.position   = rl.Vector3{0, 20, 20}
    state.camera.target     = state.player.position
    state.camera.up         = rl.Vector3{0, 1, 0}
    state.camera.fovy       = 45.0
    state.camera.projection = .PERSPECTIVE

    init_terrain(&state)
    init_enemies(&state)

    for !rl.WindowShouldClose() {
        dt := rl.GetFrameTime()

        update_player(&state, dt)
        update_enemies(&state, dt)
        update_particles(&state, dt)

        // Camera Follow
        CAM_OFFSET_Y :: 30.0
        CAM_OFFSET_Z :: 30.0
        target_cam_pos := state.player.position + rl.Vector3{0, CAM_OFFSET_Y, CAM_OFFSET_Z}
        state.camera.position = math.lerp(state.camera.position, target_cam_pos, 0.1)
        state.camera.target = state.player.position

        // --- Draw ---
        rl.BeginDrawing()
        rl.ClearBackground(rl.BLACK)

        rl.BeginMode3D(state.camera)
            
            // Draw Terrain
            map_offset := f32(MAP_SIZE * TILE_SIZE) / 2.0
            for x in 0..<MAP_SIZE {
                for z in 0..<MAP_SIZE {
                    h := state.height_map[x][z]
                    wx := (f32(x) * TILE_SIZE) - map_offset
                    wz := (f32(z) * TILE_SIZE) - map_offset
                    pos := rl.Vector3{wx, h/2.0, wz}
                    
                    color := rl.DARKGREEN
                    if h > 2.0 do color = rl.LIME
                    if h > 5.0 do color = rl.WHITE 
                    if h < -5.0 do color = rl.BLUE
                    
                    // Simple distance culling (fog-like optimization)
                    dist := rl.Vector3Distance(state.player.position, pos)
                    if dist < 60.0 {
                        rl.DrawCube(pos, TILE_SIZE, h + 20.0, TILE_SIZE, color)
                        rl.DrawCubeWires(pos, TILE_SIZE, h + 20.0, TILE_SIZE, rl.ColorAlpha(rl.BLACK, 0.3))
                    }
                }
            }

            // Draw Particles
            for p in state.particles {
                if p.active {
                    // Fade out alpha based on life
                    alpha := p.life 
                    if alpha > 1.0 do alpha = 1.0
                    col := rl.ColorAlpha(p.color, alpha)
                    rl.DrawCube(p.position, p.size, p.size, p.size, col)
                }
            }

            // Draw Enemies
            for e in state.enemies {
                if e.active {
                    // Draw a pyramid shape for the Virus
                    rl.DrawCylinder(e.position, 0.0, 1.5, 3.0, 4, rl.GREEN)
                    // Enemy Shadow
                    shadow_y := get_terrain_height(&state, e.position.x, e.position.z) + 0.1
                    rl.DrawCube(rl.Vector3{e.position.x, shadow_y, e.position.z}, 1.0, 0.05, 1.0, rl.ColorAlpha(rl.BLACK, 0.5))
                }
            }

            // Draw Player
            rl.DrawSphere(state.player.position, 0.5, rl.RED)
            forward := get_forward_vector(state.player.yaw, state.player.pitch)
            rl.DrawLine3D(state.player.position, state.player.position + (forward * 2.0), rl.YELLOW)

            // Player Shadow
            shadow_y := get_terrain_height(&state, state.player.position.x, state.player.position.z) + 0.1
            rl.DrawCube(rl.Vector3{state.player.position.x, shadow_y, state.player.position.z}, 0.8, 0.05, 0.8, rl.BLACK)

        rl.EndMode3D()

        // UI
        rl.DrawText(rl.TextFormat("Particles: %d", state.p_index), 10, 10, 20, rl.WHITE)
        rl.DrawText(rl.TextFormat("Enemies: %d", MAX_ENEMIES), 10, 30, 20, rl.GREEN)
        rl.DrawFPS(SCREEN_WIDTH - 80, 10)

        rl.EndDrawing()
    }
}
 

It appears to be the t variable for math.lerp declarations you’re doing. For example, line 154 e.position.y = math.lerp(e.position.y, desired_alt, 0.05) you’re passing in a raw value which is defaulting to be f64. It doesn’t hurt to be explicit with type declarations!

Thanks, I figured that out but I think an improvement is perhaps needed in the compiler to indicate where the error originated.