Skip to content
Kiln0.10Get Kiln

Demo run

Earlier examples from earlier Kiln versions.

Historical gallery render of Ocean salvage tug; see the poster provenance below.

Drag to orbit after opening. The model starts stationary.

For your engine. The download is a standard glTF 2.0 binary (GLB) with PBR metallic-roughness materials. glTF stores materials, not lighting or tone mapping, so your engine decides how they read. This 3D view tone-maps with Review Neutral, the Khronos PBR Neutral construction with a smaller glare offset (0.015 instead of 0.04), at exposure 0.9. Its Tone mapping control also shows ACES and Linear, for comparison.

Build measurements

Triangles
5,054
Estimated draws
87
Materials
9
Textures
0
Animation clips
0
Bounds X × Y × Z
5.34 × 3.92 × 2.56 m
Build warnings
0

Measurements come from this build.

Download this build

Runtime: 120,432 bytes. Original: 120,196 bytes.

These GLBs are build outputs of the MIT-licensed example source; no separate terms are stated for the builds.

Runtime provenance metadata
SHA-256 download hashes
Runtime GLB
41393fd9f6db0658c5343236f4c88fddf195f0b2917d9cd447b4a0da6c1b7712
Original GLB
5a578790cd252164c916774aaf7a8df904773bd443f89739c408ade6edb4cf1a
Source
c79b655b05918d92494477304821eeadaa590c8865d100ab2a15a28396f7eb03

The example source is part of the Kiln repository. Repository licence: MIT.

These GLBs are build outputs of the MIT-licensed example source; no separate terms are stated for the builds.

Recorded authorship

Muse Spark 1.3 through OpenCode ·

Recorded demo session; model credit supplied only where the retained run identifies it.

Source access
See retained demo run; no stronger isolation claim is made.
Inherited context
Not independently recorded; source-header declarations only
Starting example
Not recorded
Human input
Owner supplied direction; model-authored exported source preserved byte-for-byte.
Authoring review
Authoring feedback and shaded demo previews where available. Not a destination-runtime acceptance claim.

Gallery GPU render of this exact source. Source, artifact, image hashes and camera settings are recorded alongside the poster; the artifact hash names the GLB bytes the image was rendered from, which the downloadable rebuild reproduces byte for byte only on the platform that recorded it.

Poster camera and render record

The source behind this build

demo-ocean-salvage-tug.kiln.js
const meta = { name: 'Ocean salvage tug', category: 'vehicle', role: 'vehicle' };

// Dimensions in metres. +X forward, +Y up, +Z starboard.
// Stylized ocean salvage tug: navy hull, cream cabin/bulwark, safety-orange
// working deck trim, A-frame, winch and funnel. Keel at Y=0 (drydock sit).

const NAVY = 0x1e3a5c;
const CREAM = 0xf2e8d5;
const ORANGE = 0xff5a1f;
const RUBBER = 0x1b1b1e;
const STEEL = 0x3a4148;
const DARKSTEEL = 0x23272d;
const WINDOW = 0x22394e;
const DECKFORE = 0xe4d9c2;
const CABLE = 0x22262b;

// Plan-view outline of the hull (X forward, Z starboard), ordered
// stern-port -> bow -> stern-starboard so the loop closes across the transom.
function hullOutline(beamScale) {
  const pts = [
    [-2.4, -1.05],
    [-0.6, -1.15],
    [1.4, -0.85],
    [2.5, 0.0],
    [1.4, 0.85],
    [-0.6, 1.15],
    [-2.4, 1.05],
  ];
  return pts.map(([x, z]) => [x, z * beamScale]);
}

/** Vertical tire fender hung against the hull side. */
function tireFender(name, pos, parent, mats) {
  createPart(name, torusGeo(0.17, 0.065, 10, 20), mats.rubber, {
    position: pos, parent,
  });
}

/** Pair of bollard posts with a top plate. */
function bollard(name, x, z, deckY, parent, mats) {
  const g = createPivot(name, [x, deckY, z], parent);
  createPart('PostA', cylinderGeo(0.05, 0.06, 0.28, 12), mats.steel, {
    position: [-0.09, 0.14, 0], parent: g,
  });
  createPart('PostB', cylinderGeo(0.05, 0.06, 0.28, 12), mats.steel, {
    position: [0.09, 0.14, 0], parent: g,
  });
  createPart('TopPlate', boxGeo(0.3, 0.05, 0.12), mats.steel, {
    position: [0, 0.3, 0], parent: g,
  });
  return g;
}

function build() {
  const root = createRoot('SalvageTug');

  const mats = {
    navy: gameMaterial(NAVY),
    cream: gameMaterial(CREAM),
    orange: gameMaterial(ORANGE),
    rubber: gameMaterial(RUBBER),
    steel: gameMaterial(STEEL),
    dark: gameMaterial(DARKSTEEL),
    window: gameMaterial(WINDOW),
    deckFore: gameMaterial(DECKFORE),
    cable: gameMaterial(CABLE),
  };

  // ---- Hull: loft three plan sections (keel -> waterline -> deck edge) ----
  const hullGeo = loftProfiles([
    { profile: hullOutline(0.72), frame: { origin: [0, 0.15, 0], rotation: [0, 0, 0] } },
    { profile: hullOutline(0.92), frame: { origin: [0, 0.55, 0], rotation: [0, 0, 0] } },
    { profile: hullOutline(1.0), frame: { origin: [0, 1.05, 0], rotation: [0, 0, 0] } },
  ]);
  createPart('Hull', hullGeo, mats.navy, { parent: root });

  // Waterline boot stripe (thin loft band just above the waterline).
  // Kept slightly proud of the hull surface to avoid z-fighting.
  const stripeGeo = loftProfiles([
    { profile: hullOutline(0.945), frame: { origin: [0, 0.64, 0], rotation: [0, 0, 0] } },
    { profile: hullOutline(0.965), frame: { origin: [0, 0.76, 0], rotation: [0, 0, 0] } },
  ]);
  createPart('BootStripe', stripeGeo, mats.orange, { parent: root });

  // ---- Decks ----
  // Aft working deck (safety orange) and foredeck (cream).
  createPart('WorkingDeck', boxGeo(2.9, 0.07, 1.86), mats.orange, {
    position: [-0.95, 1.09, 0], parent: root,
  });
  createPart('ForeDeck', boxGeo(1.7, 0.07, 1.5), mats.deckFore, {
    position: [1.35, 1.09, 0], parent: root,
  });
  // Deck anti-slip strips.
  for (let i = 0; i < 4; i++) {
    createPart('DeckStrip' + i, boxGeo(2.5, 0.02, 0.05), mats.dark, {
      position: [-0.95, 1.13, -0.6 + i * 0.4], parent: root,
    });
  }

  // ---- Bulwark (cream) with orange cap rail ----
  const bulwarkH = 0.42;
  const bulwarkY = 1.05 + bulwarkH / 2;
  // Straight side walls.
  createPart('BulwarkPort', boxGeo(3.0, bulwarkH, 0.08), mats.cream, {
    position: [-0.9, bulwarkY, -1.06], parent: root,
  });
  createPart('BulwarkStbd', boxGeo(3.0, bulwarkH, 0.08), mats.cream, {
    position: [-0.9, bulwarkY, 1.06], parent: root,
  });
  // Transom wall.
  createPart('BulwarkTransom', boxGeo(0.08, bulwarkH, 2.2), mats.cream, {
    position: [-2.38, bulwarkY, 0], parent: root,
  });
  // Angled bow walls (port/starboard).
  createPart('BulwarkBowPort', boxGeo(1.5, bulwarkH, 0.08), mats.cream, {
    position: [1.55, bulwarkY, -0.78], rotation: [0, -28, 0], parent: root,
  });
  createPart('BulwarkBowStbd', boxGeo(1.5, bulwarkH, 0.08), mats.cream, {
    position: [1.55, bulwarkY, 0.78], rotation: [0, 28, 0], parent: root,
  });
  // Orange cap rail on top of each wall.
  const capY = 1.05 + bulwarkH + 0.03;
  createPart('CapPort', boxGeo(3.0, 0.06, 0.12), mats.orange, {
    position: [-0.9, capY, -1.06], parent: root,
  });
  createPart('CapStbd', boxGeo(3.0, 0.06, 0.12), mats.orange, {
    position: [-0.9, capY, 1.06], parent: root,
  });
  createPart('CapTransom', boxGeo(0.12, 0.06, 2.2), mats.orange, {
    position: [-2.38, capY, 0], parent: root,
  });
  createPart('CapBowPort', boxGeo(1.5, 0.06, 0.12), mats.orange, {
    position: [1.55, capY, -0.78], rotation: [0, -28, 0], parent: root,
  });
  createPart('CapBowStbd', boxGeo(1.5, 0.06, 0.12), mats.orange, {
    position: [1.55, capY, 0.78], rotation: [0, 28, 0], parent: root,
  });

  // ---- Rubber fenders ----
  // Vertical side fenders (cylinders) + hanging tire fenders (torus).
  const fenderX = [-1.9, -0.9, 0.1];
  for (let i = 0; i < fenderX.length; i++) {
    createPart('FenderPort' + i, cylinderGeo(0.11, 0.11, 0.55, 14), mats.rubber, {
      position: [fenderX[i], 0.82, -1.13], parent: root,
    });
    createPart('FenderStbd' + i, cylinderGeo(0.11, 0.11, 0.55, 14), mats.rubber, {
      position: [fenderX[i], 0.82, 1.13], parent: root,
    });
  }
  tireFender('TirePortA', [-1.4, 0.95, -1.2], root, mats);
  tireFender('TirePortB', [-0.4, 0.95, -1.22], root, mats);
  tireFender('TireStbdA', [-1.4, 0.95, 1.2], root, mats);
  tireFender('TireStbdB', [-0.4, 0.95, 1.22], root, mats);
  // Bow stem fender.
  createPart('StemFender', cylinderGeo(0.1, 0.1, 0.7, 12), mats.rubber, {
    position: [2.5, 0.82, 0], parent: root,
  });
  // Stern push fender (horizontal log across transom).
  createPart('SternFender', cylinderGeo(0.13, 0.13, 1.9, 14), mats.rubber, {
    position: [-2.5, 0.85, 0], rotation: [90, 0, 0], parent: root,
  });

  // ---- Cabin block (cream) with navy roof ----
  createPart('CabinBase', boxGeo(1.5, 0.95, 1.5), mats.cream, {
    position: [0.55, 1.6, 0], parent: root,
  });
  createPart('Wheelhouse', boxGeo(1.3, 0.75, 1.35), mats.cream, {
    position: [0.55, 2.45, 0], parent: root,
  });
  createPart('CabinRoof', boxGeo(1.55, 0.1, 1.6), mats.navy, {
    position: [0.55, 2.88, 0], parent: root,
  });
  // Window band (dark) around wheelhouse + front windshields.
  createPart('WindowBand', boxGeo(1.32, 0.3, 1.37), mats.window, {
    position: [0.55, 2.55, 0], parent: root,
  });
  // Mullions to break the band into windows (cream posts).
  for (let i = -1; i <= 1; i++) {
    createPart('MullionFront' + i, boxGeo(0.06, 0.32, 0.06), mats.cream, {
      position: [1.22, 2.55, i * 0.4], parent: root,
    });
  }
  // Cabin door (aft face) + portholes.
  createPart('CabinDoor', boxGeo(0.06, 0.7, 0.4), mats.navy, {
    position: [-0.23, 1.6, 0.3], parent: root,
  });
  for (let i = 0; i < 2; i++) {
    createPart('PortholePort' + i, cylinderGeo(0.09, 0.09, 0.06, 16), mats.navy, {
      position: [0.2 + i * 0.5, 1.65, -0.76], rotation: [90, 0, 0], parent: root,
    });
    createPart('PortholeStbd' + i, cylinderGeo(0.09, 0.09, 0.06, 16), mats.navy, {
      position: [0.2 + i * 0.5, 1.65, 0.76], rotation: [90, 0, 0], parent: root,
    });
  }
  // Orange cabin trim stripe.
  createPart('CabinStripe', boxGeo(1.52, 0.1, 1.52), mats.orange, {
    position: [0.55, 2.02, 0], parent: root,
  });

  // ---- Funnel (safety orange with dark cap) ----
  createPart('Funnel', cylinderGeo(0.28, 0.34, 0.7, 18), mats.orange, {
    position: [-0.55, 2.35, 0], parent: root,
  });
  createPart('FunnelCap', cylinderGeo(0.29, 0.29, 0.14, 18), mats.dark, {
    position: [-0.55, 2.75, 0], parent: root,
  });

  // ---- Mast + radar + lights ----
  createPart('Mast', cylinderGeo(0.05, 0.07, 1.1, 10), mats.steel, {
    position: [0.55, 3.45, 0], parent: root,
  });
  createPart('RadarPedestal', cylinderGeo(0.09, 0.11, 0.16, 12), mats.cream, {
    position: [0.55, 3.1, 0], parent: root,
  });
  // Radar scanner bar (stylized, static forward pose).
  createPart('RadarBar', boxGeo(0.16, 0.07, 0.85), mats.cream, {
    position: [0.55, 3.24, 0], parent: root,
  });
  createPart('RadarDome', sphereGeo(0.11, 16, 12), mats.cream, {
    position: [0.15, 3.0, 0], parent: root,
  });
  // Navigation lights: port red-ish? Keep palette: small cream masthead + orange stern.
  createPart('MastheadLight', sphereGeo(0.05, 10, 8), mats.orange, {
    position: [0.55, 4.02, 0], parent: root,
  });

  // ---- Towing winch drum on working deck ----
  const winch = createPivot('Winch', [-1.15, 1.12, 0], root);
  createPart('WinchBaseL', boxGeo(0.7, 0.18, 0.12), mats.steel, {
    position: [0, 0.09, -0.55], parent: winch,
  });
  createPart('WinchBaseR', boxGeo(0.7, 0.18, 0.12), mats.steel, {
    position: [0, 0.09, 0.55], parent: winch,
  });
  createPart('WinchFrameL', boxGeo(0.12, 0.55, 0.1), mats.orange, {
    position: [-0.25, 0.4, -0.55], parent: winch,
  });
  createPart('WinchFrameR', boxGeo(0.12, 0.55, 0.1), mats.orange, {
    position: [-0.25, 0.4, 0.55], parent: winch,
  });
  createPart('Drum', cylinderGeo(0.26, 0.26, 0.95, 20), mats.dark, {
    position: [-0.25, 0.55, 0], rotation: [90, 0, 0], parent: winch,
  });
  // Wound cable wrap (slightly larger, cable color) + flange discs.
  createPart('CableWrap', cylinderGeo(0.29, 0.29, 0.6, 20), mats.cable, {
    position: [-0.25, 0.55, 0], rotation: [90, 0, 0], parent: winch,
  });
  createPart('FlangeL', cylinderGeo(0.4, 0.4, 0.06, 20), mats.orange, {
    position: [-0.25, 0.55, -0.5], rotation: [90, 0, 0], parent: winch,
  });
  createPart('FlangeR', cylinderGeo(0.4, 0.4, 0.06, 20), mats.orange, {
    position: [-0.25, 0.55, 0.5], rotation: [90, 0, 0], parent: winch,
  });
  // Orange cable drum guards: side frames standing clear of the flanges.
  createPart('DrumGuardLegFL', boxGeo(0.08, 0.9, 0.08), mats.orange, {
    position: [-0.55, 0.45, -0.62], parent: winch,
  });
  createPart('DrumGuardLegRL', boxGeo(0.08, 0.9, 0.08), mats.orange, {
    position: [0.05, 0.45, -0.62], parent: winch,
  });
  createPart('DrumGuardLegFR', boxGeo(0.08, 0.9, 0.08), mats.orange, {
    position: [-0.55, 0.45, 0.62], parent: winch,
  });
  createPart('DrumGuardLegRR', boxGeo(0.08, 0.9, 0.08), mats.orange, {
    position: [0.05, 0.45, 0.62], parent: winch,
  });
  createPart('DrumGuardTopL', boxGeo(0.68, 0.08, 0.08), mats.orange, {
    position: [-0.25, 0.92, -0.62], parent: winch,
  });
  createPart('DrumGuardTopR', boxGeo(0.68, 0.08, 0.08), mats.orange, {
    position: [-0.25, 0.92, 0.62], parent: winch,
  });
  // Tow cable running aft off the drum toward the stern roller.
  createPart('TowCable', cylinderGeo(0.025, 0.025, 1.3, 8), mats.cable, {
    position: [-0.95, 0.5, 0], rotation: [0, 0, 78], parent: winch,
  });

  // Stern roller for the tow wire.
  createPart('SternRoller', cylinderGeo(0.09, 0.09, 0.7, 12), mats.steel, {
    position: [-2.25, 1.2, 0], rotation: [90, 0, 0], parent: root,
  });

  // ---- A-frame crane (safety orange) over the stern ----
  // Feet sit on the aft deck at the transom so the hook hangs over the water.
  const frameX = -2.3;
  // Legs lean inward in Z; feet at deck, apexes high and close together.
  createPart('AFrameLegPort', cylinderGeo(0.07, 0.09, 2.3, 12), mats.orange, {
    position: [frameX, 2.2, -0.68], rotation: [20, 0, 0], parent: root,
  });
  createPart('AFrameLegStbd', cylinderGeo(0.07, 0.09, 2.3, 12), mats.orange, {
    position: [frameX, 2.2, 0.68], rotation: [-20, 0, 0], parent: root,
  });
  // Crossbeam between apexes + top pulley block.
  createPart('AFrameBeam', cylinderGeo(0.07, 0.07, 0.75, 12), mats.orange, {
    position: [frameX, 3.28, 0], rotation: [90, 0, 0], parent: root,
  });
  createPart('CraneBlock', boxGeo(0.18, 0.24, 0.14), mats.dark, {
    position: [frameX, 3.08, 0], parent: root,
  });
  // Hook cable + hook (hanging aft of the transom, under the block).
  createPart('HookCable', cylinderGeo(0.02, 0.02, 1.2, 8), mats.cable, {
    position: [frameX - 0.05, 2.36, 0], parent: root,
  });
  createPart('Hook', torusGeo(0.09, 0.03, 8, 16), mats.steel, {
    position: [frameX - 0.05, 1.7, 0], parent: root,
  });

  // ---- Deck fittings ----
  bollard('BollardSternPort', -2.0, -0.75, 1.12, root, mats);
  bollard('BollardSternStbd', -2.0, 0.75, 1.12, root, mats);
  bollard('BollardBowPort', 1.9, -0.5, 1.12, root, mats);
  bollard('BollardBowStbd', 1.9, 0.5, 1.12, root, mats);
  // Foredeck hatch + vent.
  createPart('ForeHatch', boxGeo(0.6, 0.18, 0.6), mats.steel, {
    position: [1.35, 1.2, 0], parent: root,
  });
  createPart('Vent', cylinderGeo(0.09, 0.11, 0.4, 12), mats.orange, {
    position: [0.0, 1.5, -0.6], parent: root,
  });
  createPart('VentHead', sphereGeo(0.11, 12, 8), mats.orange, {
    position: [0.0, 1.72, -0.6], parent: root,
  });

  return root;
}

Brief and retained revisions

Recorded brief

Extended A-frame crane and orange cable drum guards

  1. Saved revision 1

    Recorded saved demo revision.

    Download saved revision 1 source

    SHA-256: 49a7df1925feceffbe224eb1bdcfc1da0694667917ceeb51de1d7abb76487d27

  2. Saved revision 2 · Shown here

    Extended A-frame crane and orange cable drum guards

    Download saved revision 2 source

    SHA-256: c79b655b05918d92494477304821eeadaa590c8865d100ab2a15a28396f7eb03