FLOORED LAZZA 1.5: John's actual first car, '82 KA Laser hatch

Built from his photos: white 5-door wedge, black bumpers + belt-line rubbing
strip, amber corner indicators, wide dark grille bezel (pale headlights vanish
on white paint without it), and the crown jewel -- rear hatch louvres, five
slats that shed one by one on impact like a dropped pie. New optional spec
keys: louvres, indicators, side_strip, wide_grille; build_cars.py now takes
`-- <id>` to rebuild a single car; screenshot harness gains cam=rear.

Reversed-stats rule applied: 54 kW carby IRL -> 21.0 power / 860 kg / 7.8 grip
here. Second-quickest thing in the game behind the Excel, and the chuckable-est.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
m3ultra 2026-07-28 16:16:32 +10:00
parent da63829e9e
commit 81ed13b1ff
6 changed files with 110 additions and 5 deletions

View File

@ -46,8 +46,10 @@ Model convention (any GLB works, hand-modelled or generated):
- Node origin 0.75 m above ground level (the controller's ride height).
- Objects named `det_*` are detachable — the game rips them off on impact
(doors, mirrors, bumpers, bonnet, boot).
- Current fleet: Datto 120Y, Gemmy TX, Kingswood HQ, VL Terbo, XF Fungoon, Excel Hilton.
Piss-take names on purpose: real model names and badges are trademarks.
- Current fleet: 30 shitboxes, from the Datto 120Y to the FLOORED LAZZA 1.5 ('82
Laser hatch, rear louvres that shed slat-by-slat — John's actual first car,
built from his photos). Piss-take names on purpose: real model names and
badges are trademarks.
To improve a silhouette: supply a side-on photo + front three-quarter photo, tweak the
profile points in the spec, rebuild, screenshot.

BIN
cars/lazza_ka/car.glb Normal file

Binary file not shown.

View File

@ -0,0 +1,45 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://bbottmecxqhu1"
path="res://.godot/imported/car.glb-edc9533c079c72a53d09ab51d6c7dcf4.scn"
[deps]
source_file="res://cars/lazza_ka/car.glb"
dest_files=["res://.godot/imported/car.glb-edc9533c079c72a53d09ab51d6c7dcf4.scn"]
[params]
nodes/root_type=""
nodes/root_name=""
nodes/root_script=null
mesh_library/use_node_names_as_mesh_names=false
array_mesh/deduplicate_surfaces=true
nodes/apply_root_scale=true
nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false
nodes/use_name_suffixes=true
nodes/use_node_type_suffixes=true
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=false
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
materials/extract=0
materials/extract_format=0
materials/extract_path=""
_subresources={}
gltf/naming_version=2
gltf/embedded_image_handling=1
gltf/texture_map_mode=1

13
cars/lazza_ka/stats.tres Normal file
View File

@ -0,0 +1,13 @@
[gd_resource type="Resource" script_class="CarStats" load_steps=2 format=3]
[ext_resource type="Script" path="res://src/car_stats.gd" id="1"]
[resource]
script = ExtResource("1")
display_name = "FLOORED LAZZA 1.5"
model_path = "res://cars/lazza_ka/car.glb"
engine_power = 21.0
mass = 860.0
grip = 7.8
length = 3.96
width = 1.61

View File

@ -35,6 +35,12 @@ func _run() -> void:
game.cam.look_at(game.car.global_position + Vector3.UP * 0.4)
await process_frame
await process_frame
elif opts.get("cam", "") == "rear":
game.set_process(false)
game.cam.global_position = game.car.global_position + game.car.global_basis * Vector3(2.6, 1.6, 6.5)
game.cam.look_at(game.car.global_position + Vector3.UP * 0.4)
await process_frame
await process_frame
elif opts.get("cam", "") == "top":
game.set_process(false)
var alt := float(opts.get("alt", "150"))

View File

@ -1,6 +1,7 @@
"""Procedural Aussie shitbox factory.
Run: /Applications/Blender.app/Contents/MacOS/Blender -b -P tools/build_cars.py
(append `-- <id> [<id>...]` to rebuild only those cars)
Each car: side-silhouette profile extruded across the width (body + greenhouse),
cylinder wheels, and separate det_* panel objects (doors, mirrors, bumpers,
@ -15,6 +16,7 @@ side-on photos per car when a shape needs to get closer.
"""
import os
import math
import sys
import bpy
import bmesh
from mathutils import Matrix
@ -30,7 +32,9 @@ LIGHT_R = (0.7, 0.1, 0.08, 1)
# L, W, heights: nose/belt/tail, lengths: bonnet/boot, roof height + rear roof end,
# axle overhangs, wheel radius, paint, chrome bumpers, hatch rear.
# Optional: doors=2, bonnet_color (two-tone accent), roof_cap (vinyl roof), spoiler.
# Optional: doors=2, bonnet_color (two-tone accent), roof_cap (vinyl roof), spoiler,
# louvres (hatch-glass slat stack, each slat sheds on impact), indicators (amber
# corner blinkers), side_strip (black rubbing strip along the belt line).
CARS = {
"datto_120y": dict(L=3.92, W=1.50, nose=0.62, belt=0.78, tail=0.72,
bonnet=1.00, boot=0.85, roof=1.32, roof_rear=0.75,
@ -84,6 +88,13 @@ CARS = {
ohang_f=0.85, ohang_r=0.95, wheel=0.29,
paint=(0.70, 0.72, 0.58, 1), chrome_bumpers=True, hatch=False,
doors=2, van=True),
# John's actual first car: '82 KA Laser 1.5 hatch, white, rear louvres.
# Gutless IRL (54 kW carby) -> reversed-stats rule makes it a weapon here.
"lazza_ka": dict(L=3.96, W=1.61, nose=0.58, belt=0.79, tail=0.90,
bonnet=1.02, boot=0.35, roof=1.37, roof_rear=0.72,
ohang_f=0.79, ohang_r=0.72, wheel=0.26,
paint=(0.92, 0.92, 0.89, 1), chrome_bumpers=False, hatch=True,
louvres=True, indicators=True, side_strip=True, wide_grille=True),
# mokes use the open-tub builder: style="moke"
"mini_merk": dict(style="moke", L=3.05, W=1.42, nose=0.60, tub=0.66, canopy=1.38,
bonnet=0.85, ohang_f=0.55, ohang_r=0.55, wheel=0.26,
@ -215,6 +226,19 @@ def build(car_id, s):
parts.append(box("det_bumper_f", (0, f + 0.05, bottom + 0.16), (W + 0.06, 0.15, 0.16), bumper_mat))
parts.append(box("det_bumper_r", (0, r - 0.05, bottom + 0.16), (W + 0.06, 0.15, 0.16), bumper_mat))
if s.get("louvres") and s["hatch"]:
# venetian-blind slat stack over the hatch glass; every slat is its own
# det_ part, so a rear-ender sheds them one by one like a dropped pie
ty, tz = boot_y + s["roof_rear"], s["roof"] # glass top edge
ly, lz = rr_y, rr_z # glass bottom edge
g_ang = math.atan2(tz - lz, ty - ly)
for i in range(5):
t = 0.14 + i * 0.18
sy = ty + (ly - ty) * t
sz = tz + (lz - tz) * t + 0.045
parts.append(box("det_louvre_%d" % i, (0, sy, sz),
(W * 0.80, 0.20, 0.022), dark, rot_x=-g_ang * 0.45))
if s.get("van"): # cargo box from cab rear to tail, plus rip-off tailgate
parts.append(box("van_box", (0, (boot_y + r) / 2, (s["belt"] + s["roof"]) / 2),
(W * 0.94, boot_y - r - 0.05, s["roof"] - s["belt"]), paint))
@ -240,13 +264,23 @@ def build(car_id, s):
(W * 0.86, cap_f - cap_r + 0.1, 0.035), material("vinyl", s["roof_cap"], rough=0.9)))
if s.get("spoiler"): # bootlid ducktail
trim.append(box("trim_spoiler", (0, r + 0.22, s["tail"] + 0.05), (W * 0.8, 0.28, 0.09), paint))
grille_w = W * (0.78 if s.get("wide_grille") else 0.5) # wide = lights sit in a dark bezel band
trim += [
box("trim_grille", (0, f + 0.01, s["nose"] - 0.14), (W * 0.5, 0.05, 0.12), dark),
box("trim_grille", (0, f + 0.01, s["nose"] - 0.14), (grille_w, 0.05, 0.14), dark),
box("trim_light_fl", (-W * 0.33, f + 0.02, s["nose"] - 0.13), (0.24, 0.05, 0.13), material("lf", LIGHT_F)),
box("trim_light_fr", (W * 0.33, f + 0.02, s["nose"] - 0.13), (0.24, 0.05, 0.13), material("lf2", LIGHT_F)),
box("trim_light_rl", (-W * 0.32, r - 0.02, s["tail"] - 0.14), (0.26, 0.05, 0.12), material("lr", LIGHT_R)),
box("trim_light_rr", (W * 0.32, r - 0.02, s["tail"] - 0.14), (0.26, 0.05, 0.12), material("lr2", LIGHT_R)),
]
if s.get("indicators"):
amber = material("amber", (0.95, 0.55, 0.05, 1))
trim.append(box("trim_ind_l", (-W * 0.44, f + 0.02, s["nose"] - 0.13), (0.11, 0.05, 0.12), amber))
trim.append(box("trim_ind_r", (W * 0.44, f + 0.02, s["nose"] - 0.13), (0.11, 0.05, 0.12), amber))
if s.get("side_strip"):
strip_len = L * 0.80
for sx in (-1, 1):
trim.append(box("trim_strip_%s" % ("l" if sx < 0 else "r"),
(sx * (hw + 0.015), -L * 0.02, 0.52), (0.03, strip_len, 0.055), dark))
ax_f, ax_r = f - s["ohang_f"], r + s["ohang_r"]
wx = hw - 0.10
@ -337,9 +371,14 @@ def build_moke(car_id, s):
print("built", car_id, "->", path)
only = set(sys.argv[sys.argv.index("--") + 1:]) if "--" in sys.argv else set()
built = 0
for car_id, spec in CARS.items():
if only and car_id not in only:
continue
if spec.get("style") == "moke":
build_moke(car_id, spec)
else:
build(car_id, spec)
print("done: %d cars" % len(CARS))
built += 1
print("done: %d cars" % built)