8 new verbs (table now 115): find_model (DISTINCT ids, not ranked duplicates), find_palette (matched set from one pack), model, kits, cast, road_network, town, dungeon. game.find was ALREADY TAKEN by entity-by-tag lookup — the duplicate-name test caught the clash before it shipped, and find_model/ find_palette now match the agent TOOL names, so the model's knowledge transfers between the tool it calls and the verb it writes. Verbs run synchronously (search and layout are pure CPU); only GLB load and draw need a host, so placements queue through the same mechanism as audio and particles — which also means a scene composes headlessly with no renderer attached. Tiles carry their own collider from the kit pitch, so scripted props are as solid as hand-placed ones. THE MOST IMPORTANT EDIT WAS A DELETION. splashgame.md said "Everything is procedural... No image, model, or audio files" — the doc was actively telling the model it had no models, which is why generated games were bare primitives while 4,442 models sat unused. Replaced with an instruction to reach for the library before game.box, three rules (never place result #1 five times; one art pack per region; generate layouts rather than hand-placing) and a wrong-vs- right example. A test asserts that claim cannot come back. Two bugs found by probing the REAL library rather than reasoning: - town() would have placed ZERO buildings, silently: it selects TileRole::Building, but every role-less model mapped to Prop — and city-kit-suburban is 40 whole buildings with no parsed roles. A role-less model is genuinely ambiguous (a building on a lot, or a cone at a kerb), so kit_from_index now takes a KitUse hint. Against the real library: 104 buildings, 136 road tiles, 0 adjacency errors - the index folds crossroads and T-junctions into one `junction` role, but a 4-way cell needs four open edges; a T standing in for a crossroad leaves a road stub pointing at nothing. Disambiguated by name village.splash is the scenery counterpart to racing.splash: a town, a wood of four different conifers, a dungeon, a playable character — and not one model id written by hand. NOT BOUND, and why: game.tree/rock/blob and game.scatter generate MESHES, and set_models takes an asset id, not geometry — there is no mesh-upload path for generated meshes yet, so binding them would have meant faking it. Additive once a generated-mesh queue exists. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
77 lines
2.5 KiB
Text
77 lines
2.5 KiB
Text
// Wanderhome — the scenery counterpart to racing.splash.
|
|
//
|
|
// racing.splash is the model answer for GAMEPLAY: the engine drives the cars
|
|
// and counts the laps, so the file is layout and rules. This one is the model
|
|
// answer for a PLACE: the engine lays the streets, picks the artwork and makes
|
|
// it solid, so the file is intent.
|
|
//
|
|
// Every prop here is a real model from the stock library. Nothing is a
|
|
// coloured box, and no model id is written by hand — they all come back from
|
|
// game.find_model, which returns DIFFERENT models rather than one repeated.
|
|
|
|
let SEED = 11
|
|
let ROADS = "kenney/city-kit-roads"
|
|
let HOUSES = "kenney/city-kit-suburban"
|
|
|
|
game.sky({})
|
|
game.sun({time_of_day: 8.0})
|
|
|
|
// ---- the town: streets with buildings fronting them --------------------
|
|
// Junction types are never named here — a crossing produces a crossroad and a
|
|
// tee produces a T, purely from how the grid connects.
|
|
let town = game.town({
|
|
roads_kit: ROADS,
|
|
buildings_kit: HOUSES,
|
|
extent: 18,
|
|
block: 6,
|
|
density: 0.7,
|
|
seed: SEED,
|
|
})
|
|
game.log("town laid")
|
|
|
|
// ---- a wood around it --------------------------------------------------
|
|
// Four DIFFERENT conifers, not one conifer four times: `count` is what stops
|
|
// a wood looking stamped.
|
|
let trees = game.find_model("pine tree", {count: 4, seed: SEED})
|
|
let rocks = game.find_model("rock stone", {count: 3, spread: "variants", seed: SEED})
|
|
|
|
for i in 0..40 {
|
|
let a = i * 0.157
|
|
let r = 46.0 + (i % 7) * 2.5
|
|
game.model(trees[i % 4], {
|
|
pos: vec3(cos(a) * r, 0.0, sin(a) * r),
|
|
yaw: a * 2.0,
|
|
})
|
|
}
|
|
for i in 0..9 {
|
|
let a = i * 0.7
|
|
game.model(rocks[i % 3], {
|
|
pos: vec3(cos(a) * 38.0, 0.0, sin(a) * 38.0),
|
|
yaw: a,
|
|
})
|
|
}
|
|
|
|
// ---- somewhere to go ---------------------------------------------------
|
|
// The same generator gives a crypt, a cavern or a space station depending
|
|
// only on the kit; every room is reachable by construction.
|
|
let crypt = game.dungeon({
|
|
kit: "kenney/modular-dungeon-kit",
|
|
extent: 20,
|
|
min_room: 5,
|
|
seed: SEED,
|
|
})
|
|
game.log("crypt laid")
|
|
|
|
// ---- someone to be -----------------------------------------------------
|
|
let hero = game.character({
|
|
pos: vec3(0, 2, 0),
|
|
size: vec3(0.6, 1.7, 0.6),
|
|
color: #x4f8fe8,
|
|
speed: 6.0,
|
|
})
|
|
game.camera({third_person: hero, height: 1.6, boom: 9, pitch: -0.3})
|
|
game.text("hint", "Arrows to walk — find the crypt", {anchor: "top"})
|
|
|
|
game.on_tick(|dt, input| {
|
|
game.drive(hero, {move_x: input.move_x, move_z: input.move_z, jump: input.jump})
|
|
})
|