creative/psx-game-forge/SKILL.md

name: psx-game-forge description: >- Create and refine original PS1/PS2-era (and modern-nostalgia) 3D game assets, environments, characters, props, and the Blender-to-runtime seam (deterministic Blender Python generators, GLB export, verification in the real runtime). Use for first builds of a retro-styled scene or asset, "match this reference's quality" refinement passes, and diagnosing why an exported GLB reads wrong in the runtime (atlas UVs, texel density, foliage that vanishes at playable height, camera and collision-name contracts). Not for 2D/pixel art, and never for reproducing recognizable assets, layouts, or proprietary designs from a reference.

psx-game-forge

When to use this

Use this skill to create or refine original PS1-, PS2-, or turn-of-the-century 3D game assets, environments, characters, props, Blender generators, scene composition, or the export seam into a runtime. Use it for both first builds and "match this reference's quality" passes.

The era is a range, not one house style. Establish whether the target is PS1, early PS2, late PS2, or modern nostalgia before deciding geometry, texture, and lighting budgets. Preserve the reference's qualities, not recognizable assets, layouts, or proprietary designs.

Workflow

Establish the target era and the project's contracts before touching geometry: which of PS1, early PS2, late PS2, or modern nostalgia the reference is; the scene brief; the generator, export path, runtime loader, and every caller of a naming or behaviour contract you are about to change. For a short reference video sample several interior frames directly; for a long corpus use footage-index. Compare silhouette, material response, texel density, foliage/prop density, lighting, palette, and focal hierarchy, and write a short quality target for this project — one project's coordinates, palette, biome, or asset list never becomes a reusable convention.

For a reference-led refinement keep a project-local packet: sampled source frames, original concept images spanning gameplay rooms and production views, a prompt manifest, and a contact sheet. Copyrighted source frames stay local; original generated concepts persist as reviewable project inputs.

Build in descending visual scale — terrain and composition, hero props, vegetation and secondary forms, textures, clutter, lighting, then effects — and fix shared scene systems before polishing isolated objects. Keep the Blender source editable; when agentic regeneration matters, a deterministic Blender Python build sits beside the authored scene and the runtime format (GLB) is exported from that source of truth. Preserve the project's object-name contract; a new one uses only the prefixes the runtime needs (COL_ collision surfaces, CHR_ movable character roots).

Acceptance is a wide composition view plus a ground-level detail view compared against the reference, fixing the largest visible gap each pass (two or three render-look-fix loops is normal), and then the asset loaded in the actual runtime with textures, transparency, object names, collisions, cameras, and logs checked there. An exported file alone is not proof.

Quality bar

  • Nostalgia is an art-direction constraint, not permission for placeholder art.
  • Silhouette variety and layered density usually matter before raw polygon count.
  • Flat colors alone rarely match finished PS2-era work; use intentional UVs, packed textures or vertex color, material variation, and controlled roughness.
  • A scene reads at three scales: macro landforms, medium props/vegetation, and micro ground cover or surface breakup. Missing one makes the result feel empty.
  • Keep focal areas dense and traversal paths legible. Uniform scattering reads as procedural noise.
  • Set budgets from the chosen era and camera distance; do not invent a generalized asset catalog before a second scene proves the shared shape.

Blender and GLB gotchas

  • Blender 5.1 uses BLENDER_EEVEE as the Eevee render-engine identifier.
  • Generated image textures must be packed before GLB export. Blender primitives have UVs; meshes created with from_pydata need explicit per-loop UVs.
  • Blender Generated/Object texture coordinates can render correctly without round-tripping through glTF. Drive exported atlases from UV coordinates and compare the GLB in the real runtime before accepting the Blender preview.
  • Do not repeat a packed atlas tile with UVs above 1; that samples neighboring tiles. Map local overlay geometry into 0–1 UV space or bake the repetition, then confirm the exported GLB rather than trusting only the Blender render.
  • Stretching one atlas tile across a whole landscape destroys playable-height texel density. Keep compact atlas tiles for props, give broad ground roles small seamlessly repeatable maps with world-scale UVs, project steep faces against elevation, and assert both edge equality and UV span before export.
  • A texture tile can have enough numeric contrast and still read as procedural wallpaper when long cracks or seams span the full tile and repeat on every asset. Keep distinctive marks local, layer discrete brush dabs over multi-scale fields, assert sampled contrast on focal tiles, and inspect a close exported-runtime view before accepting the atlas.
  • A Principled material's base-color default is ignored when an atlas image drives Base Color. Give surfaces with different palette roles distinct tiles, then verify the exported GLB.
  • Terrain-following ribbons need samples across their width, mitered joins, a small shell offset, and disabled cast shadows; edge-only strips can cut through cross-slopes or read as raised slabs.
  • Repainting atlas tiles cannot hide a hard terrain-material boundary. Derive shared boundary edges from adjacent face material indices, feather them with one route-aware verge mesh, and assert coverage plus upward winding without assuming the terrain is flat.
  • Exclude preview cameras and lights from GLB when the runtime owns lighting.
  • Stable object names are data. Assert required names and a nontrivial export size in the generator so a visually successful but unusable export fails the build.
  • Weight procedural foliage motion with local UVs or an explicit per-vertex attribute. World-position weights make the same asset animate differently after placement.
  • Third-person camera collision must exclude every descendant of the player root. Keep solid props out of the initial camera boom between spawn and camera.
  • Keep the locomotion/grounding root separate from the visual rig. Animate named child pivots so gait bob and limb swing cannot corrupt collision, terrain height, or first-person camera placement.

Headless build pattern:

"/Applications/Blender.app/Contents/MacOS/Blender" --background --factory-startup \
  --python <generator.py> -- <scene-brief.json> <output.glb>

Scene lessons

The feature-by-feature catalogue (terrain and cliffs, water and shoreline, foliage, caves and routes, structures, characters and camera) is references/scene-lessons.md; open it when the scene has that feature. The principles behind every entry:

  • Judge from the playable camera, on the authored route, at gameplay distance. Overhead and wide previews prove silhouette and hide almost everything else: buried foliage, flat ground cards, blocked portals, seams outside the visible water mesh, a backlit key light flattening correct materials. Every acceptance render starts where the player stands, and one close oblique pass catches construction errors (reversed cladding, empty backing panels, cropped hair or footwear) a wide view cannot.
  • Assert geometry in the generator, not by eye. Names, export size, world-space entrance width and headroom, route coverage and offset band, minimum room separation, face winding toward the camera, elevation offsets from a terrain shell — compute them from generated vertex bounds and fail the build when they miss, so a render that looks fine but cannot be played does not ship. Check the acceptance camera against those bounds too: a camera that starts inside a canopy or a portal's depth validates nothing.
  • Variety comes from correlated families and seeded substreams, not independent draws. Palms, rocks, clouds and plant patches read as designed when height, lean, width and material move together as a few silhouette families; a branch that skips a random draw silently recomposes every later element, so give each element its own deterministic substream and compare the whole composition after any change.
  • Keep structural mass in one material family; localize age and detail. Alternating courses, exterior shoulders in the dark recess material, or evenly stacked modules turn ruins, cave mouths and cliffs into machinery or picket walls. Vary mass, height and lean; reserve the darkest material for recesses; put age into irregular stains, broken edges and hanging growth.
  • Fix the largest visible gap in the shared system before polishing the object. A shoreline seam is usually the terrain depression, not the water boundary; a hero prop that reads as a toy is a proportion problem no surface relief rescues; a route blocked by later scatter is a corridor reservation missing from every downstream pass.
  • Rig and camera contracts are data. Camera collision excludes every descendant of the player root; the locomotion root stays separate from the visual rig so gait bob cannot corrupt grounding; eye height lives in one runtime constant that moves with the sculpted eye line; default third-person framing is validated against the concept, not aimed straight at the player.