# The Last Hearth — woodland travelers and botanical pixel catalogue ## Authoritative fauna-23 / catalogue-100 art contract (2026-11-03) This section supersedes earlier catalogue counts and package totals, not the character-layer or legacy OBJ contracts. Original MIT procedural drawings and meshes only: no downloaded assets, fonts, libraries or engine launch. Runtime wildlife integration remains the Lua owner's responsibility; these generators do not register monsters, change spawning, collision boxes or gameplay. ### Exact animated fauna handoff [The fauna generator](../scripts/fauna-art.mjs) exports frozen `FAUNA_NAMES`, `FAUNA_ANIMATIONS`, `FAUNA_ART_MANIFEST`, `FAUNA_MATERIALS` and pure methods `faunaB3D(id)`, `faunaTexture(id)`, `faunaBounds(id)`, `faunaPortraitSheet(frame=9)`. Unknown IDs and frames fail closed. Every result is deterministic and fresh. Exactly these **23** IDs, in manifest/portrait row-major order: hare, deer, fox, rabbit, red_squirrel, bank_vole, wood_mouse, hedgehog, badger, roe_deer, stoat, weasel, otter, robin, blackbird, blue_tit, barn_owl, mallard, common_frog, smooth_newt, brown_trout, bumblebee, earthworm. Each has **hearth_fauna_<id>.b3d** and **hearth_fauna_<id>.png**; the latter is an opaque **64×64** nearest-neighbor semantic atlas. These 23 PNGs are deliberately **not** worldArt tiles. Old hearth_hare.obj, hearth_fox.obj, hearth_deer.obj and their textures remain byte compatible and generated; runtime can migrate to the new names independently. Every model has one root, one textured mesh, outward-wound flat-normal solids, half-texel inset UVs, normalized rigid bone weights, no scale animation and **100 real skeletal frames at 24 FPS**. B3D key indices 1–100 map to Luanti frames 0–99. `visual_size={x=1,y=1}`, **10 model units/node**, feet/lowest bind surface at **Y=0**, forward +Z. `faunaBounds(id).min/max` are authoritative **bind-mesh node bounds**, not guessed collision or conservative animated bounds. They are also serialized per animal; do not infer scale from portrait cells. Bind bounds in nodes below are rounded to five decimals for review; the exported values and generated manifest retain full precision. All minimum Y values are 0. | ID | Minimum X, Y, Z | Maximum X, Y, Z | | --- | --- | --- | | hare | -0.13750, 0, -0.34150 | 0.13750, 0.81400, 0.37004 | | deer | -0.31625, 0, -0.68300 | 0.31625, 1.59470, 0.65081 | | fox | -0.15000, 0, -0.86240 | 0.15000, 0.68560, 0.50280 | | rabbit | -0.15400, 0, -0.25954 | 0.15400, 0.55090, 0.31371 | | red_squirrel | -0.07500, 0, -0.46744 | 0.07500, 0.59240, 0.24266 | | bank_vole | -0.07000, 0, -0.17039 | 0.07000, 0.19600, 0.17507 | | wood_mouse | -0.06000, 0, -0.31045 | 0.06000, 0.23860, 0.16250 | | hedgehog | -0.11000, 0, -0.21173 | 0.11000, 0.25000, 0.23755 | | badger | -0.19000, 0, -0.45078 | 0.19000, 0.49240, 0.47589 | | roe_deer | -0.22375, 0, -0.49176 | 0.22375, 1.22850, 0.48443 | | stoat | -0.06000, 0, -0.45146 | 0.06000, 0.24620, 0.26954 | | weasel | -0.04250, 0, -0.26846 | 0.04250, 0.18080, 0.19111 | | otter | -0.12500, 0, -0.70711 | 0.12500, 0.38840, 0.39778 | | robin | -0.16460, 0, -0.21895 | 0.16460, 0.37210, 0.17388 | | blackbird | -0.18440, 0, -0.30653 | 0.18440, 0.41750, 0.20140 | | blue_tit | -0.12500, 0, -0.17516 | 0.12500, 0.31670, 0.13705 | | barn_owl | -0.28340, 0, -0.27369 | 0.28340, 0.64340, 0.27500 | | mallard | -0.27350, 0, -0.43790 | 0.27350, 0.47040, 0.25550 | | common_frog | -0.09900, 0, -0.15026 | 0.09900, 0.14750, 0.22272 | | smooth_newt | -0.04000, 0, -0.34231 | 0.04000, 0.10140, 0.16295 | | brown_trout | -0.10450, 0, -0.35520 | 0.10450, 0.30450, 0.24000 | | bumblebee | -0.14400, 0, -0.06500 | 0.14400, 0.12250, 0.10640 | | earthworm | -0.02000, 0, -0.19127 | 0.02000, 0.04000, 0.19127 | | Clip | Inclusive engine frames | Authored action | | --- | --- | --- | | idle | 0–19 | breathing, looking, ear/tail attention; segmented worm motion | | locomotion | 20–39 | linked upper/lower limb strides, tail follow-through; worm joint wave | | feed | 40–59 | head/neck dip and bird peck; small head/segment sampling motion | | flight_swim | 60–79 | mirrored wing flaps with secondary flight feathers; aquatic tail beats/fins | | rest | 80–99 | settled head/limbs, low-amplitude breathing and tail/segment movement | All five ranges loop with matching endpoint poses. No engine-side fake motion is needed. The shared `flight_swim` range on non-flying terrestrial mammals is an **alert looking reserve**, not invented flight/swimming. The rest range is a settled standing/perched posture, **not a speculative lying-down or curled skeletal deformation**. Frog/newt limbs paddle; fish have a hinged caudal fan, paired pectorals and dorsal fin; bees have six legs and thoracic wings; the earthworm has twelve serially parented body joints. No bones are driven merely because another species has similarly named appendages. Hare ears and hind feet, compact rabbit, squirrel's raised brush, vole's short tail versus mouse's long tail, hedgehog spine blocks, broad badger, tall antlered deer versus short roe antlers, slender stoat/weasel, low otter with cream muzzle, robin breast, blackbird bill, blue tit cap, owl facial discs, duck neck/webbed feet/speculum and amphibian low profiles are authored species landmarks, not a single body recoloured. The restrained coat/cream/earth/mineral palette matches the Minecraft/Vintage Story-like block world without copying either game's art. The sixteen **16×16** material patches occupy four row-major columns: | Row | 0 | 1 | 2 | 3 | | --- | --- | --- | --- | --- | | 0 | coat | belly | face | ear | | 1 | foot | tail | wing | eye | | 2 | nose | stripe | feather | beak | | 3 | fin | spot | horn | membrane | Badger face stripes, cream otter underside, stoat black tail tip, fox cream tip, bee bands, trout spots, wing bars and feather veins are semantic markings, not whole-model tints. All atlas texels have alpha 255 (solid low-poly appendages). `generateAssets()` writes **fauna-art.json** at package root: format 1, `animations`, `animals` (ID, exact filenames, dimensions, units, visual size, bind bounds, posture, clips, MIT provenance), and `portraitSheet` metadata. Every output uses the existing guarded staging writer and package hash inventory. The portrait path inside staging is **mods/hearth/textures/hearth_fauna_contact_sheet.png**: **512×768**, four columns of 128px cells, 23 animals plus one paper cell, frame 9. It is an actual CPU triangle rasterization of skinned geometry with atlas sampling and flat lighting, **not a texture swatch, font illustration or engine screenshot**. `faunaPortraitSheet(65)` previews the same geometry's flight/swim pose. Each cell fits its own posed bounds, so cells are an identity review, not relative scale. ### Catalogue 100 and three additional cube-tree material families The JavaScript art catalogue now matches all **100 exact IDs and Latin names** in the read-only Lua BOTANY catalogue. Every taxon has active and dormant 32px sprites: **200** provenance records in **botany-art.json**, each including `botanicalLatin`, group, morphology, state, filename, dimensions and MIT author. Group counts: trees 13, shrubs 14, grasses 10, herbs 22, ferns_mosses 11, fungi 10, aquatic 10, lichens 10. The twenty additions are: - crab_apple — Malus sylvestris; wild_cherry — Prunus avium; field_maple — Acer campestre. - honeysuckle — Lonicera periclymenum; ivy — Hedera helix; gorse — Ulex europaeus; broom — Cytisus scoparius. - dandelion — Taraxacum officinale; daisy — Bellis perennis; red_clover — Trifolium pratense; birdsfoot_trefoil — Lotus corniculatus; ribwort_plantain — Plantago lanceolata; selfheal — Prunella vulgaris; knapweed — Centaurea nigra; oxeye_daisy — Leucanthemum vulgare; field_scabious — Knautia arvensis; teasel — Dipsacus fullonum; cow_parsley — Anthriscus sylvestris; garlic_mustard — Alliaria petiolata. - holly_fern — Polystichum setiferum (catalogue contract ID; soft shield-fern). Original 80 drawings and approved leaf PNG bytes are retained. New silhouettes have distinct flowerheads, twining honey tubes, climbing ivy rootlets, gorse spines, broom switches, ligulate dandelion rays, trifoliate clovers, plantain's anther ring, teasel cones, compound parsley umbels, brassica siliques and forked fern pinnae. Dormant specimens lose flowers and fruit, keeping species-specific dry structure. Existing runtime pixel-wind plant waving is unchanged. All thirteen cube-tree IDs: oak, birch, pine, willow, alder, beech, aspen, rowan, hazel, spruce, crab_apple, wild_cherry, field_maple. Each has log_top, log_side, leaves and planks. Deciduous species also have leaves_spring, leaves_autumn and leaves_winter. Cherry has horizontal lenticels; crab apple fine fissures; maple interrupted cork diamonds. **46** compatibility bark/leaf atlases cover all species/seasons, including underscore IDs parsed by the anchored suffix contract `/_atlas(?:_(spring|autumn|winter))?$/`, never positional underscore splitting. No new unused irregular-tree OBJ meshes are generated; the 29 legacy flora OBJ exports and the 23 old species-stage meshes remain frozen. The world contact sheet now has **421 tiles**, **512×864**, **16 columns**, sorted filenames as the row-major legend, height **ceil(count/16)*32**. The bounded **1–512** tile guard remains intact. Its 61 additions are 40 botanical sprites plus 21 cube-tree materials/seasonal leaves. Fauna never consumes those cells. The two-swatch minimal fixture closes at **675 PNGs and 63 models**: **37 OBJ + 26 B3D** (3 players and 23 fauna), with exact manifest-derived inventories and package-integrity verification, not loose minimum-count checks. Validation is Node-only: independent B3D chunk parsing, winding, normals, UVs, weights, every keyed pose, forward-kinematic displacement for all five clips, loop seams, species landmarks, encoded texture pixels, botanical catalogue equality, unique silhouettes, approved old leaf hashes, package bytes and output link refusal. No engine render, performance, live animation or spawn claim. Final focused run: **60 passed, 0 failed, 0 cancelled, 0 skipped**, Node **24.22.0**, **160.80 seconds**, exit 0 and empty stderr. Suites: fauna art, world art, flora art, existing assets and character art. Both actual skinned mesh sheets (idle frame 9 and flight/swim frame 65) and the 421-tile world sheet were visually inspected outside the source tree. No manual binary files added. CodeGuard boundaries: no credentials or certificates introduced; SHA256 is used only for deterministic integrity checks; positive ID/frame validation, bounded allocations and existing symlink/junction-safe staging writes are retained. Optional audio codec decode/regeneration was not configured; existing Ogg framing/CRC/hash checks passed. Directory-junction refusal passed; the existing file-symlink subcase lacked Windows symlink privilege. ## Authoritative character art contract (2026-11-03) **Revision note (2026-11-04):** the 2026-11-03 option counts and rig/mesh-size figures in this section are superseded by the expanded character catalogue below. ### Expanded woodland traveler update (2026-11-04) The current public body allowlist is `compact`, `lean`, `balanced`, `broad`, `sturdy`; all remain cosmetic, equal-height silhouettes using the same rig and collision. Added original face linework (`lined`), hair styles (`curly`, `topknot`, `undercut`), facial hair (`goatee`, `braided_beard`), garments (`hooded_cloak`, `travel_coat`, `wool_mantle`), accents (`satchel`, `tool_belt`, `pauldrons`, `hood`), and a `birthmark`. The current generated manifest is 258 distinct validated filenames (including transparent `none` variants), and the expanded player B3D contains 384 vertices and 192 triangles. The model adds an original draped shoulder mantle, belt, back satchel, and projecting nose/ears; no player mechanics or tool attachment names are changed. Current Lua and art builders must use the exact texture order implemented by `C.preview()` and `characterLayers()`; hood accents intentionally overlay hair. See [character-creator.md](character-creator.md) for the current option list. This section supersedes earlier **player** appearance and package-total statements below; botanical art itself is unchanged. Character work owns only [scripts/media.mjs](../scripts/media.mjs), [scripts/assets.mjs](../scripts/assets.mjs), [scripts/character-art.mjs](../scripts/character-art.mjs), [tests/character-art.test.mjs](../tests/character-art.test.mjs), [tests/assets.test.mjs](../tests/assets.test.mjs) and this document. No Lua, gameplay, collision, camera, engine, dependency or source-media edits. All drawings are original procedural MIT work, with no downloaded art, network, image service, third-party model, global tint or extra character-layer entities. ### Woodland traveler grammar and unchanged UV contract Quiet mineral cloth, undyed linen, worn walnut leather and natural hair replace the old flat blockperson skin. Small collars, rolled cuffs, stitched hems, plackets, leather panels, coat openings, boot laces/soles and sparse linework give the traveler a grounded, practical silhouette. Faces remain readable at eight pixels: two iris landmarks, restrained brows, nose and mouth, no oversized white cartoon eyes. Skin choices are independent of facial features and clothes. Every runtime layer is **64×64 straight RGBA**, nearest-neighbor, with **0/255 alpha** and zero RGB behind transparent pixels. The atlas is still eight columns of eight-pixel tiles, with the original half-texel UV inset. Tile coordinates: | Row | Column 0 | Column 1 | Column 2 | | --- | --- | --- | --- | | 0 | face | hair (head back/top) | side (both head sides) | | 1 | tunic (torso front/ends/sides) | back | sleeve | | 2 | trousers | boot | skin (hands/head underside) | | 3 | metal | wood | wrap | The six base skins are opaque over **all 4096 pixels**, including a linen underlayer and fallback trousers/boots. Skin tone changes exactly the four exposed head/hand tiles, **256 pixels**, not the clothing or tool swatches. Face overlays are skin-neutral lines, not light-colored face masks. Hair paints the scalp, sides and hairline; long hair and braids also cover upper back and shoulder pixels. Garments and accents paint only torso/back/sleeve tiles; trousers and boots paint only their own tiles. No overlay touches tool tiles or unused atlas space. The former metal/wood/wrap pixels are **byte-exact** in all base skins and in the default **hearth_person.png**, so existing held-tool OBJ materials keep their appearance. Existing tool geometry and icon art are retained. ### Exact IDs and generated layer manifest `CHARACTER_OPTIONS` exports these frozen allowlists, without aliases: | Group | IDs | | --- | --- | | bodies | compact, balanced, broad | | skin | porcelain, sand, honey, olive, umber, ebony | | faces | calm, angular, soft, weathered | | eyes | hazel, brown, green, blue, grey | | hairStyles | cropped, sidepart, wavy, braids, long, shaved, none | | hairColors | ash, flax, chestnut, auburn, black, silver | | facialHair | none, stubble, moustache, short_beard, full_beard | | garments | tunic, workshirt, jerkin, coat | | clothColors | sage, teal, slate, mauve, ochre, rust, linen, charcoal | | trousers | earth, slate, moss, charcoal | | boots | walnut, black, tan | | accents | none, scarf, apron, sash, vest | | details | none, freckles, scar, freckles_scar | The **170** entries in `CHARACTER_ART_MANIFEST` use these exact filename templates. Each row includes every Cartesian product, even transparent `none`: | Filename template | Count | | --- | ---: | | hearth_char_skin_<skin>.png | 6 | | hearth_char_face_<face>.png | 4 | | hearth_char_eyes_<eyes>.png | 5 | | hearth_char_hair_<style>_<hairColor>.png | 42 | | hearth_char_beard_<style>_<hairColor>.png | 30 | | hearth_char_garment_<style>_<cloth>.png | 32 | | hearth_char_trousers_<trousers>.png | 4 | | hearth_char_boots_<boots>.png | 3 | | hearth_char_accent_<style>_<accentColor>.png | 40 | | hearth_char_detail_<detail>.png | 4 | Accent colors use the same eight `clothColors`; beard colors use the same six `hairColors`. Exactly **21** images are intentionally empty: six hair-none, six beard-none, eight accent-none and one detail-none. All **149 nonempty layers** are distinct; equivalent empty-none color variants are deliberately identical. Each manifest record includes `filename`, `kind`, `id`, `color` (null if absent), `width`, `height`, `empty`, `license`, `author`. `generateAssets()` writes the layers into the staged texture directory, and a root-level **character-art.json** provenance document with `format:1`, `options`, `meshes` and `images`. All outputs use the existing guarded `writeAsset`, `relativeName` and `safePath`; no source-worktree assets are generated. The document and every layer/mesh are included in the existing SHA256 package inventory. It adds **171 PNGs** (170 layers + portrait sheet) and **two B3Ds** to previous totals: **576 fixed PNGs**, plus source-dependent compatibility swatches, **37 OBJ + three B3D**. The two-swatch fixture totals **578 PNGs**. ### Pure methods and Lua composition handoff [The renderer module](../scripts/character-art.mjs) exports: - `characterLayer(filename)` → fresh `{width:64,height:64,data:Uint8Array}`; unknown filenames fail closed. `characterArt()` → deterministic filename Map of all 170 fresh images. - `characterLayers(options={})` → ten exact filenames in compositing order; `characterTexture(options={})` → those names joined by `^`, ready for one Luanti mesh texture. Only allowlisted fields/values are accepted; no path, prototype key, inherited options object or arbitrary file input. - `compositeCharacter(options={})` → pure fresh opaque 64×64 RGBA atlas using the same crisp source-over order. No I/O or global recoloring. - `characterMesh(body='balanced')` → exact mesh filename. - `portraitSheet()` → opaque **512×64** RGBA contact sheet, eight assembled 64px portrait cells. `CHARACTER_PORTRAITS` gives the exact left-to-right selections. Portraits sample actual composited face, tunic, sleeve, skin, trousers and boot tiles; they are paper dolls, **not atlas swatches or 3D engine-render screenshots**. Staging writes **hearth_character_contact_sheet.png**. Selection fields and defaults (`DEFAULT_CHARACTER`): `body:balanced`, `skin:sand`, `face:calm`, `eyes:hazel`, `hair:sidepart`, `hairColor:chestnut`, `beard:none`, `garment:tunic`, `cloth:teal`, `trousers:earth`, `boots:walnut`, `accent:scarf`, `accentColor:sage`, `detail:none`. The old **hearth_person.png** is now this complete default composite, not a removed compatibility material. **Required left-to-right texture order:** skin → face → eyes → garment → trousers → boots → accent → hair → beard → detail. In filename form: hearth_char_skin_<skin>.png ^ hearth_char_face_<face>.png ^ hearth_char_eyes_<eyes>.png ^ hearth_char_garment_<garment>_<cloth>.png ^ hearth_char_trousers_<trousers>.png ^ hearth_char_boots_<boots>.png ^ hearth_char_accent_<accent>_<accentColor>.png ^ hearth_char_hair_<hair>_<hairColor>.png ^ hearth_char_beard_<beard>_<hairColor>.png ^ hearth_char_detail_<detail>.png. Remove the displayed spaces around carets when composing. Clothing **must precede hair**, or long locks disappear under the upper-back/shoulder garment pixels. Runtime appearance UI, persistence and attachment application belong to the Lua owner; this work only supplies assets and the exact builder contract. ### Mesh profiles, rig and attachment invariants `playerB3D(profile='balanced')` accepts exactly `compact`, `balanced`, `broad`. Mesh filenames: **hearth_person.b3d** (balanced), **hearth_person_compact.b3d**, **hearth_person_broad.b3d**. Each retains one textured mesh, **240 vertices, 120 outward-wound triangles**, rigid weight 1 per vertex, original UV assignments and **17.5 model-unit height** (feet 0, head top 17.5 = 1.75 nodes). No collision, camera/eye-height, reach, global scale or root-motion change. Balanced head changes from X ±1.75 / Z ±1.5 to **X ±1.6 / Z ±1.4**; Y remains 13.5–17.5. Torso shoulder half-width 2.2 → **2.15**, half-depth 1.25 → **1.18**; bottom waist is 86% width/depth. Arms half-width .85 → **.78**, depth .9 → **.84**, tapering to 90% width/95% depth at the wrist. Legs half-width .95 → **.91**, depth 1 → **.95**, tapering to 90% width/94% depth at the ankle. Normals are recomputed from actual tapered face planes. Hands, boots and all vertical extents stay unchanged. These are subtle tapered ten-part solids, not additional joints or extra geometry. Compact/broad multiply torso, upper-arm and leg widths by **.92 / 1.08**. Broad uses a slightly fuller 90% waist width; all waist depths stay 86%. Head, hands, boots, depths, height and rig pivots remain identical across profiles. No profile moves an attachment hand to accommodate a wider body. All nine bones, hierarchy and bind positions remain exactly unchanged: `Root`, `Body`, `Head`, `Arm_Right`, `Hand_Right`, `Arm_Left`, `Hand_Left`, `Leg_Right`, `Leg_Left`. Arm pivots X ±3.1/Y13; hands local [0,-4.8,0] under their arms. All bones retain the exact 100 keyed poses, B3D frames 1–100 / engine 0–99. Clips unchanged: idle 0–19@12, walk 20–39@24, run 40–59@32, strike 60–69@28, guard 70–79@12, swim 80–99@20. Held grip stays at `Hand_Right`, local origin, rotation {x=90,y=0,z=0}; the shared tool atlas is retained. ### Validation and security boundary CodeGuard was read and applied: no credentials, secrets, certificates or crypto protocols introduced; existing SHA256 stays integrity-only. Positive enum/key validation, bounded allocations, pure offline drawing and existing link/junction write refusal are retained. No shell/eval, new dependencies or downloads. Tests independently parse generated PNG CRC/data and all three B3D chunk trees, check every exact option, semantic mask, all 170 encoded layers, 149 nonempty distinct images, meaningful skin differences, 21 intentional empty layers, paper-doll samples, deterministic regeneration/package hashes, rig/keys/weights, head/waist geometry and byte-exact held-tool swatches. No engine launch, client rendering, FPS, collision-course or live-world claim. Final focused character + asset run: **24 passed, 0 failed, 0 cancelled, 0 skipped**, exit 0, empty stderr, **65.60 seconds**, Windows Node **24.22.0**. All five JavaScript syntax checks passed. All six owned files were read back from disk for trailing whitespace and completion; the eight-portrait sheet was visually reviewed. Optional full audio codec regeneration was not configured; existing audio framing/CRC/hash checks passed. Directory-junction refusal passed; the file-symlink subcase could not run without Windows symlink privilege. Git diff checking was unavailable from this subprocess environment, so no Git diff-clean claim is made. Runtime Lua appearance integration is not tested here. ## Authoritative catalogue-80 art contract (2026-11-02) This section supersedes all folded historical counts and mesh integration advice. Only [scripts/world-art.mjs](../scripts/world-art.mjs), [scripts/flora-art.mjs](../scripts/flora-art.mjs), [scripts/assets.mjs](../scripts/assets.mjs), [tests/world-art.test.mjs](../tests/world-art.test.mjs), [tests/flora-art.test.mjs](../tests/flora-art.test.mjs) and this document are owned. The actual [botany catalogue](../mods/hearth/botany.lua), [biomes](../mods/hearth/biomes.lua) and [diversity](../mods/hearth/diversity.lua) were read, never edited. No engine tests or client launch are part of this art work; runtime cube/season closure belongs to the content and seasons owners. ### Original botanical grammar All art is original procedural drawing under the [MIT license](../LICENSE.txt). No copyrighted third-party images, downloads, fonts, random noise, dependencies or generated-image service. Every specimen is drawn at **32×32**, straight RGBA, nearest-neighbour with **crisp 0/255 alpha**, zero hidden RGB and one-pixel empty borders. Small natural sage, bark, buff and mineral palettes dominate; flowers, fruit and reproductive details are restrained accents. These are stylized field journal specimens, **not identification, toxicity or eating advice**. No plate, meal, bite, food badge or edibility permission is depicted, including toxic taxa. - **Trees**: oak rounded lobes and stalked acorns; birch serrate diamonds and catkins; pine long paired needle sprays; willow pendant narrow blades; alder round notched leaves and conelets; beech oval parallel-veined leaves; aspen scalloped round leaves and petioles; rowan pinnate leaflets and clustered pomes; hazel heart-shaped leaves and husked nuts; spruce short radial needles and long pendant cones. All ten get inventory/fieldguide specimens independently of cubes. - **Shrubs**: low branching twig clusters, arching thorny bramble/rose canes, upright raspberry, opposite compound elder, rigid blackthorn, lobed hawthorn, spiny evergreen holly, juniper needle whorls, three-lobed guelder rose. Fruit attachment, clusters and leaves differ, not just hue. - **Grasses and graminoids**: dense tussocks below diagnostic panicles/spikes; sedge has angular triangular culm faces, reedmace cylindrical brown heads, cock's-foot one-sided clumps, timothy a tight long spike, vernal-grass short golden ovals, Yorkshire-fog soft pink panicles, hair-grass airy rays, reed tall jointed stems and broad blades, rush lateral inflorescences on smooth culms. - **Herbs**: trifoliate clover and stolon, serrate opposite nettle, heather scale sprays, one-sided bluebell bells, yarrow flat corymb and divided leaves, foxglove tubular bells and rosette, meadowsweet cream froth, garlic star umbel, cowslip nodding yellow tubes, anemone single flower over divided-leaf whorl. - **Ferns/mosses**: triangular three-part bracken, fine lady-fern, robust male-fern, entire hart's-tongue straps, polypody pinnae/sori versus low moss cushions: sphagnum capitula, haircap shoots/capsules, plait-moss feathers, swept fork-moss leaves, broad-leaved star-moss shoots. They do not share a generic bush glyph. - **Fungi**: warted fly-agaric, scabrous birch-bolete stem, chestnut bay cushion, folded chanterelle funnel, lilac blewit, shaggy cylindrical inkcap, banded turkey tail shelves, lateral oyster fans, tufted ringed honey caps, kidney polypore. No invented puffball is substituted: none occurs among these ten catalogue IDs. - **Aquatic**: duckweed small paired fronds/roots; **round or oval notched lily pads**, yellow cup versus many white tepals (not lotus horns); crowfoot divided foliage, mint globe, forget-me-not curled cyme, brooklime raceme, starwort terminal rosette, rootless hornwort forked whorls, long-petioled pondweed ovals. - **Lichens**: fruticose reindeer forks, flattened oakmoss straps, hanging beard filaments, map-lichen crust islands, broad dog-lichen lobes/rhizines, lungwort reticulation, orange sunburst discs, grey hammered shields, distinct slender mealy and stout pebbled pixie cups. Crust, foliose and branched forms differ. Inactive images are **per ID**, not one shared dormant swatch: blooms/fruit are omitted, leaves reduced or twigs exposed, fungi collapse to stems/shelves, growth settles into a smaller dry silhouette. Persistent evergreen/moss/lichen images also have an explicit inactive fallback, but **do not imply those species must become dormant in Winter**; the seasonal registrar decides when each is used. ### Exact image and provenance manifests `BOTANY_CATALOGUE` lists **80 unique exact IDs and 80 unique Latin names**, transcribed from the actual Lua `add()` records, in catalogue order. Eight groups of ten: trees, shrubs, grasses, herbs, ferns_mosses, fungi, aquatic, lichens. Each record includes `id`, `latin`, `group`, diagnostic `morphology`. `BOTANY_ART_NAMES` contains those exact 80 IDs; `BOTANY_SPRITE_NAMES` expands them to **160** stems. Each has prefix `hearth_botany_` and PNG extension: the ID alone for active art, the ID plus `_dormant` for inactive art. `BOTANY_ART_MANIFEST` annotates **each of the 160 PNG outputs** with exact `filename`, `id`, **`botanicalLatin`**, `group`, `morphology`, `state`, dimensions, license and authorship. Generation writes this deterministically as a root-level JSON provenance document named **botany-art.json** inside the private output tree. It is included in the hash-verified package. No Lua evaluation, source-loop guessing, network or platform-specific botanical database is needed. Tests parse the actual plain Lua catalogue independently and compare every ID/Latin pair. `worldArt()` now returns **360 distinct images**: **160 world/ecology tiles, 34 item icons, three UI tiles, three animal atlases, 160 catalogue specimens**. This adds **192** images to the previous 168: twenty new tree faces, twelve new deciduous seasonal leaf faces, and 160 catalogue specimens. Generated package outputs: **405 fixed PNGs**, plus source-dependent compatibility swatches. The minimal fixture has two swatches, thus **407 PNGs**. The fixed set is 360 sprites + **38 opaque 64×32 atlases** (four generic, 34 species) + contact sheet + four tool icons + player skin + crosshair alias. There remain **29 legacy flora OBJ files**, **37 OBJ total plus one animated player B3D**. No new tree meshes were authored; player, tools, wildlife and fifteen Ogg files are unchanged. ### Exact ten-tree cube material and season contract All stems in this table take prefix `hearth_` and PNG extension. Each row explicitly gives log end, log side, leaves and crafted boards in that order. | Species / botanical identity | Log top | Log side | Leaves | Planks | Additional leaf seasons | | --- | --- | --- | --- | --- | --- | | oak / Quercus robur | oak_log_top | oak_log_side | oak_leaves | oak_planks | spring, autumn, winter | | birch / Betula pendula | birch_log_top | birch_log_side | birch_leaves | birch_planks | spring, autumn, winter | | pine / Pinus sylvestris | pine_log_top | pine_log_side | pine_leaves | pine_planks | none; evergreen | | willow / Salix alba | willow_log_top | willow_log_side | willow_leaves | willow_planks | spring, autumn, winter | | alder / Alnus glutinosa | alder_log_top | alder_log_side | alder_leaves | alder_planks | spring, autumn, winter | | beech / Fagus sylvatica | beech_log_top | beech_log_side | beech_leaves | beech_planks | spring, autumn, winter | | aspen / Populus tremula | aspen_log_top | aspen_log_side | aspen_leaves | aspen_planks | spring, autumn, winter | | rowan / Sorbus aucuparia | rowan_log_top | rowan_log_side | rowan_leaves | rowan_planks | spring, autumn, winter | | hazel / Corylus avellana | hazel_log_top | hazel_log_side | hazel_leaves | hazel_planks | spring, autumn, winter | | spruce / Picea abies | spruce_log_top | spruce_log_side | spruce_leaves | spruce_planks | none; evergreen | Season images append `_spring`, `_autumn`, `_winter` to the **leaf stem**. Beech/aspen/rowan/hazel add twelve seasonal faces; spruce is evergreen like pine. Forty base faces plus 24 seasonal leaf faces are explicit outputs. New bark conveys smooth grey beech, diamond-scarred pale aspen, lenticellate rowan/hazel and scaly spruce; endgrain is eccentric rings and boards show restrained joints. All older generic/species leaf drawing code and encoded PNG bytes are preserved; tests pin the six approved base leaf PNG hashes, plus the original generic RGBA SHA256 `6ca3e11b7e8e98f46220db92a4dbe729515835c3ae39c76ac3003fa5494f3f4c`. **New roots/stages use cubes**, no irregular canopy OBJ. Log face order is top, top, side, side, side, side; existing `facedir` controls axis orientation. Each seasonal woody-stage copy must retain that cube tile list and must not switch to an atlas/mesh. Canopy cubes use the species/season leaf faces; tree seedlings, inventory and guide can use catalogue sprites. Runtime geometry, migration and seasonal swaps are other owners' work, not implemented by this art generator. `LEGACY_MESH_SPECIES` freezes the original five species. Existing generic and species OBJ exports are **deprecated compatibility assets only**, retained until the native-closure owner excludes unused files. Do not infer use from packaging or add five new sculptural models. `SPECIES_ATLAS_NAMES` has all ten species: eight deciduous × four seasons + two evergreen × one = 34. Generic four + 34 = 38. New atlases are compatibility/review bytes, **not cube stage tiles**. Each keeps own bark byte-exact at left and own seasonal foliage at right with opaque underpaint. Evergreen non-summer atlas requests fail closed. ### Bounded sorted review layout `contactSheet()` accepts **1–512** validated RGBA 32px tiles: **16 columns**, 512px wide, height `ceil(count/16)*32`, maximum 1024px. Current **360-image 512×736** sheet has **23 rows**, eight unused final cells. Native pixels are not resampled. Full filenames sort in codepoint order regardless of Map insertion order; zero-based sorted index `i` maps to column `i%16`, row `floor(i/16)`. Cells are composited on the journal checker, not opaque placeholder squares. This formula and the complete named/Latin manifest are the authoritative legend, not an obsolete manually maintained 10-column table. A separate botanical review can select the 160 `hearth_botany_` images: the same sorted 16-column layout makes **512×320** (ten rows), placing active and dormant specimens next to each other. ### Safety and validation boundary Every PNG, retained OBJ, review sheet and provenance output passes the existing `writeAsset` / `relativeName` / `safePath` path guards in private staging. Links and junctions are refused; tile count, dimensions and decode allocations are bounded. Source scanning remains mod-Lua-only. No secrets, cryptographic protocol or external certificates are introduced; SHA256 is package integrity, not trust. Focused Node suites check the actual 80 ID/Latin pairs, 160 unique images, 80 unique active and 80 unique inactive alpha masks, palette/occupancy/padding, diagnostic botanical landmarks, preserved leaf PNGs, all cube faces and atlases, determinism, independent PNG CRC/decode, exact package bytes and output link refusal. Existing legacy topology/UV budgets remain checked without claiming the models are used. **No engine rendering, mapgen, seasonal runtime closure or performance certification is claimed.** Final focused run: **39 passed, 0 failed, 0 cancelled, 0 skipped**, exit 0, empty stderr, **80.84 seconds**, Windows Node **24.22.0**. All three asset suites were included; optional external audio codec regeneration was not configured. The **512×736** complete sheet and **512×320** botanical sheet (also enlarged nearest-neighbour 2× for inspection) were generated under the system temporary review directory. Windows directory-junction refusal and exact fixture-package bytes passed. Existing leaf PNG fingerprints passed without changing their art.
Historical art milestones — superseded counts and tree-mesh advice # The Last Hearth — moonlit botanical field journal ## Current botanical extension — authoritative contract (2026-11-02) This section supersedes the folded historical snapshot below. Only [world-art.mjs](../scripts/world-art.mjs), [flora-art.mjs](../scripts/flora-art.mjs), [assets.mjs](../scripts/assets.mjs), [world-art.test.mjs](../tests/world-art.test.mjs), [flora-art.test.mjs](../tests/flora-art.test.mjs) and this document are owned. The exact runtime contracts were read from [biomes.lua](../mods/hearth/biomes.lua), [diversity.lua](../mods/hearth/diversity.lua), [world-generation.md](world-generation.md) and [ecosystems.md](ecosystems.md). No gameplay, engine, package metadata or live-world edits are included. ### Original realistic botanical pixel grammar All drawings and sculptures are original procedural source under the project's [MIT license](../LICENSE.txt). No downloaded assets, image/model libraries, network calls or random/hash noise. Mocha journal shadows support muted natural mineral, loam, sage and timber colours; flowers and metal highlights remain small. These are stylized natural materials, not photographic scans or plant-ID advice. - **Base stone is unjointed parent rock**: irregular connected mineral microfacets, not masonry, mortar outlines or a brick tessellation. Granite has feldspar and quartz grains; basalt tight dark vesicles; limestone pale fossil/lens marks; slate oblique cleavage; sandstone interrupted bedding and compact grains. - **Soil is granular aggregate**: small discontinuous compacted lenses, crumbs and organic marks, never full-width cartoon stripes. Forest floor has fragmented litter, podzol needle litter, alluvium fine sediment/rounded grains, peat dark organic fibres, alpine turf low mineral-interspersed growth. - **Grass top is dense, non-symbolic tuft mosaic** across the whole surface, not isolated outlined blades. Grass side has a variable organic cap and fine roots into the same mineral soil body. Sand uses low-contrast ripples; gravel has rounded clasts, scree angular fragments, snow sparse compressed wind marks. - **Bark has wandering interrupted fissures** and branch scars, not repeated straight planks. Endgrain rings are eccentric, warped and split. Birch is pale with dark horizontal lenticels; pine scaly warm bark; willow fine dark furrows; alder compact gray-brown bark. Planks remain visibly crafted with subdued joints and flowing grain. Only foundations/weirs and named rock bricks use masonry. - The approved generic **leaf texture is byte-for-byte unchanged**. Its RGBA SHA256 is `6ca3e11b7e8e98f46220db92a4dbe729515835c3ae39c76ac3003fa5494f3f4c`. New species leaves have distinct shapes: lobed oak, small airy birch, pine needle sprays, narrow hanging willow and rounded alder. Spring adds sparse buds; Autumn copper/ochre/wine; Winter open woody forks with no green leaf fill. - Copper seams show oxidized green beside dull copper; quartz crystalline milky facets; coal broken dark seams. Resource and tool icons have their own padded silhouettes. The copper charge retains ore with the existing binding/tag grammar. Every drawing is native 32×32 straight-alpha RGBA, nearest-neighbor, with an explicit restrained palette. Natural rock/substrate and ore-host faces are opaque; leaves/understory/items use crisp 0/255 alpha and zero hidden RGB. Item borders remain empty. Existing window alpha-95 glass, water seams and legacy interfaces are retained. Sand is horizontally seamless; water is seamless on both axes. Different materials use different geometry rather than one recoloured tessellation. ### Exact output inventory `worldArt()` returns **168 distinct 32×32 images**: **128 world/ecology tiles, 34 item icons, three UI tiles and three animal atlases**. The increase from 95 is 73 sprites: 41 biome faces, 12 deciduous seasonal leaf faces, 14 understory sprites and six resource/tool icons. `WORLD_NAMES`, `BIOME_WORLD_NAMES`, `SPECIES_NAMES`, `ROCK_NAMES`, `SPECIES_SEASONAL_NAMES`, `UNDERSTORY_NAMES`, `DORMANT_UNDERSTORY_NAMES`, `PLANT_NAMES`, `ITEM_NAMES` are explicit bounded manifests. All stems below take `hearth_` plus the PNG extension: - For each of granite, basalt, limestone, slate, sandstone: the rock stem and its `_bricks` stem (10); forest_floor, podzol, alluvium, peat, alpine_turf, gravel, scree, snow (8); quartz_vein, copper_ore_vein, coal_ore_vein (3). - For each oak, birch, pine, willow, alder: `_log_top`, `_log_side`, `_leaves`, `_planks` (20). These **41 faces plus item_quartz, item_copper_ore, item_coal** implement the exact 44-PNG biome contract, not aliases or compatibility swatches. - Deciduous oak/birch/willow/alder `_leaves_spring`, `_leaves_autumn`, `_leaves_winter` (12). Pine has no seasonal leaf/atlas/mesh duplicates. - clover, nettle, heather, bluebell, sedge, bulrush, moss, lichen; the first six also have `_dormant` (14). Dormant sprites lose flowers/green and reduce growth; bluebell reuses the dormant sprite outside Spring. Moss/lichen remain persistent. - Additional `item_copper_ingot`, `item_copper_charge`, `item_copper_pick` (3). Quartz crystal, copper ore, coal, ingot, bound charge and pick are six distinct icons, including when generated from a minimal source with no asset references. `generateAssets()` additionally writes **21 opaque 64×32 tree atlases** (four generic plus 17 species), the **320×544 contact sheet**, four existing tool icons, the 64×64 player skin and engine crosshair alias. Thus there are **196 named PNG outputs plus the separately source-dependent compatibility swatches**. No output discovery depends on expanding Lua loops. Existing sound/player/tool generation and fifteen Ogg files remain unchanged. `FLORA_NAMES` contains **29 OBJ meshes**: six retained generic flora models plus 23 species models. Together with three animals, four held tools and the fallback player this is **37 OBJ files**, plus the existing animated player B3D. ### Species sculpture, scale and seasonal contract All flora uses 10 model units/node, node-centre origin, ground exactly Y=-5 model units (-0.5 nodes), `visual_scale=1`. Mature height is at most five nodes and horizontal footprint at most ±1.5; young forms fit 2.5 high and ±0.75. Seedlings are below one node high and ±0.3. Collision/placement/growth policy is the ecosystem owner's responsibility, not inferred from the visual canopy. Oak has broad spreading forks and an asymmetric multi-lobed crown. Birch has a slim elevated bole and separated small airy lobes. Pine has tapered branch whorls and a narrow leader, with daylight between tiers. Willow has long descending terminal wood and six pendant leaf curtains under a spreading crown. Alder has multiple upright leaders and a compact rounded riparian crown. These are closed volumes, not crossed planes, leaf billboards or oversized cubes. For every species generate `_seedling`, `_young`, `_mature` OBJ stems. Deciduous species also generate `_young_winter`, `_mature_winter`; all original woody parts are retained exactly and all foliage omitted. Seedlings retain their ordinary mesh in Winter as runtime specifies. Pine stays evergreen. Generic deadwood stays shared; species provenance is metadata, not extra deadwood art. | Species | Seedling vertices / triangles | Young and mature vertices / triangles | Each winter young/mature | | --- | --- | --- | --- | | Oak | 84 / 152 | 332 / 596 | 176 / 308 | | Birch | 84 / 152 | 300 / 540 | 144 / 252 | | Pine | 110 / 200 | 546 / 988 | none | | Willow | 84 / 152 | 558 / 1004 | 272 / 476 | | Alder | 84 / 152 | 274 / 492 | 144 / 252 | `SPECIES_MESH_NAMES` and `SPECIES_ATLAS_NAMES` enumerate exact outputs. Each species uses its own `_atlas` PNG; deciduous trees additionally use `_atlas_spring`, `_atlas_autumn`, `_atlas_winter`. `treeAtlas(art, season, species)` preserves the original two-argument API. Unsupported species/seasons fail closed; seasonal pine requests are rejected. Left half is own-species bark, byte-identical through seasons; right half own-season foliage over opaque semantic underpaint. Half-texel inset UVs and explicit outward normals are retained. Winter faces sample only bark; canopy holes are gaps between lobes, never torn transparent faces. ### Sorted contact-sheet legend `contactSheet()` sorts by complete filename using codepoint order, independent of input Map insertion order. It accepts **1–320 validated 32px RGBA tiles**, always 10 columns, height `ceil(count/10)*32` (maximum 1024px). The current 168-tile sheet is **320×544** with two unused checker cells. It contains every sprite, not the separate 64×32 tree atlases. Native pixels are not resampled. Rows and columns below are one-based; stems retain `item_` and omit only `hearth_` / PNG extension. | Row | Columns 1 → 10 | | --- | --- | | 1 | alder_leaves · alder_leaves_autumn · alder_leaves_spring · alder_leaves_winter · alder_log_side · alder_log_top · alder_planks · alluvium · alpine_turf · autumn_mushrooms | | 2 | autumn_mushrooms_dormant · basalt · basalt_bricks · bedroll · berry_bush · berry_bush_bare · birch_leaves · birch_leaves_autumn · birch_leaves_spring · birch_leaves_winter | | 3 | birch_log_side · birch_log_top · birch_planks · bluebell · bluebell_dormant · bulrush · bulrush_dormant · clay_bank · clover · clover_dormant | | 4 | coal_ore_vein · copper_ore_vein · crop_1 · crop_2 · crop_3 · crop_4 · crosshair · deadwood · deer · door | | 5 | drying_rack · dung · fern · fern_dormant · fire · fire_lit · fish_trap · forest_floor · foundation · fox | | 6 | frame · fungi · grain_patch · granite · granite_bricks · grass_side · grass_top · gravel · hare · heather | | 7 | heather_dormant · hotbar · iron_ore_vein · item_autumn_mushrooms · item_berries · item_charcoal · item_charcoal_charge · item_clay · item_clay_jar · item_clay_jar_charge | | 8 | item_cloak · item_coal · item_cooked_fish · item_cooked_food · item_copper_charge · item_copper_ingot · item_copper_ore · item_copper_pick · item_dried_fish · item_dried_food | | 9 | item_dried_food_charge · item_firestarter · item_fish_bait · item_fish_drying_charge · item_grain · item_grain_seed · item_iron_axe · item_iron_ingot · item_iron_ore · item_quartz | | 10 | item_raw_fish · item_raw_food · item_smelt_charge · item_spring_greens · item_vessel · item_water_clean · item_water_raw · leaves · leaves_autumn · leaves_spring | | 11 | leaves_winter · lichen · limestone · limestone_bricks · litter · log_side · log_top · loose_stone · mature_tree · meadow_grass | | 12 | meadow_grass_dormant · moss · nettle · nettle_dormant · oak_leaves · oak_leaves_autumn · oak_leaves_spring · oak_leaves_winter · oak_log_side · oak_log_top | | 13 | oak_planks · pantry · peat · pine_leaves · pine_log_side · pine_log_top · pine_planks · planks · podzol · quartz_vein | | 14 | reeds · roof · sand · sandstone · sandstone_bricks · sapling · scree · sedge · sedge_dormant · selected | | 15 | slate · slate_bricks · snow · soil · spring_greens · spring_greens_dormant · stairs · sticks · stone · storage | | 16 | thatch · torch · tree_seedling · vent · wall · water · weir_stone · wildflowers · wildflowers_dormant · willow_leaves | | 17 | willow_leaves_autumn · willow_leaves_spring · willow_leaves_winter · willow_log_side · willow_log_top · willow_planks · window · young_tree · (two unused) | ### Packaging, security and verification boundary Every named PNG/OBJ still passes through `writeAsset`, `relativeName`, `safePath` and the existing guarded staging directories. Input/output links and junctions remain refused; sheet and PNG memory are bounded. Minimal-source generation and integrity-verified packaging check every new atlas and every mesh byte. No new dependencies, secrets or certificates; SHA256 is integrity, PNG CRC32 a checksum, neither an authenticity guarantee. This applies CodeGuard secret avoidance and bounded safe-file handling without introducing a network/security subsystem. The focused suites now cover exact independent manifests, actual content icon coverage, approved leaf hash, rock versus brick structure, distributed turf, aggregate soil/root transitions, multi-radius growth rings, unique palettes/masks, understory dormancy, species dimensions/architecture, all 29 flora meshes' finite indexed outward nondegenerate triangles, flat normals, closed oriented manifold parts, positive volume, UV insets, every seasonal woody-part preservation, exact atlas source pixels, complete package bytes, independent PNG decode and sorted sheet cells. Larger species have explicit 640-vertex/1200-triangle ceilings; legacy budgets and all topology assertions remain. The bounds are real serialized geometry, not assumed from profile labels. Node-only validation and contact-sheet inspection do **not** certify engine rendering, growth, seasonal swaps, mapgen, collision, performance or live-world acceptance. No engine was launched. Optional codec regeneration requires its existing configured tool; Ogg framing/CRC/hash checks remain part of the unchanged media suite. Latest extension verification: **35 passed, 0 failed, 0 cancelled, 0 skipped**, exit 0, empty stderr, **43.87 seconds** with the absolute Node **24.22.0** executable. All five JavaScript/test modules passed syntax checks. The generated **168-image 320×544** sheet was inspected separately. Optional codec decode/regeneration was not configured; Ogg framing/CRC/hash checks passed. Windows file-symlink privilege was unavailable; directory-junction refusal passed.
Historical pre-extension art snapshot (superseded counts, geometry and sheet order) The following records the earlier 95-sprite/six-mesh milestone only. In particular its old masonry base stone, broad earth bands, 160-tile limit, contact-sheet order, unchanged generic bark statement and latest-run counts are no longer current. Use the authoritative extension contract above for generation and integration. ## Direction and authorship An original, quiet 32px pixel-art system: a naturalist's field journal left open beside a warm hearth. Warm bark, layered earth, weathered stone and sage foliage form the world; moonlit Mocha shadows hold it together. Mauve, teal and pale gold are small points of interest, **not neon terrain fills**. The design was checked against eom.dev's local [Catppuccin stylesheet](../../www/htdocs/static/catppuccin.css) and [home stylesheet](../../www/htdocs/static/home.css): Mocha base/mantle, restrained raised surfaces, mauve accents and pale text. These are color/design references, not borrowed illustrations. All pixel silhouettes, material motifs, meshes and UV layouts here are newly authored in [world-art.mjs](../scripts/world-art.mjs), under the project's [MIT license](../LICENSE.txt). No downloaded art, animal model, font, image library, runtime dependency, network fetch, random seed or generative-image output is involved. ### Palette hierarchy | Role | Palette anchors | Use | | --- | --- | --- | | Journal shadows | `#181825`, `#1e1e2e`, `#313244`, `#45475a` | Outlines, deep joints, interface | | Warm ground | `#443a38`, `#665044`, `#876957`, `#ad7e68` | Earth strata, clay banks, plaster | | Bark and cut timber | `#433b36`, `#685346`, `#92765a`, `#ad916b`, `#c6ad83` | Fissures, growth rings, sawn boards | | Botanical greens | `#384c43`, `#556e55`, `#7d9570`, `#a4b28b` | Sage leaves with dark undersides | | Spring growth | `#526b50`, `#8fa67c`, `#bac59a`, `#c19caa` | Fresh sage blades, sparse dusty-pink buds | | Autumn canopy | `#64454c`, `#a26c49`, `#b59458`, `#845365` | Muted wine shadows, copper and ochre clusters; no green | | Thatch and grain | `#766848`, `#a39061`, `#c6b581` | Overlapping straw and ripe seed heads | | Water and iron | `#334952`, `#425e66`, `#638187`, `#909f9f` | Quiet ripples and small cool metal edges | | Reserved accents | `#cba6f7`, `#94e2d5`, `#f9e2af`, `#fab387` | Flowers, selection, small sparks; never biome fills | The complete immutable `PALETTE` export is authoritative. Each tile uses a small subset. Plain dark pixels are intentional visual rest, not missing detail. ### Pixel grammar - Exactly 32×32 logical pixels; nearest-neighbor filtering, no antialiasing. - Connected 2–8px color clusters instead of independent hash/static noise. - Material-specific geometry: irregular stone courses, broad soil layers, long bark fissures, concentric end-grain, peg-and-joint planks, timber braces, plaster cracks, tied overlapping thatch, inset storage and pantry jars. - Light comes from upper left; edges catch warm parchment or cool moonlight. - Plants have stems, paired leaves, distinct negative spaces and intentional asymmetric silhouettes. Fruit is a mauve cluster, not a glowing block. - Food and tools read by silhouette first. Charges retain the input ingredient and add a diagonal binding and pale-gold paper tag; do not confuse a queued process with a finished resource. Clean water has a crisp sparkle; raw water has muted suspended marks as well as a different color. - Water is static, fully opaque source art with sparse horizontal wavelets. Its opposite edges match on both axes. Apply the game's desired liquid alpha in node configuration. Sand/clay strata match left/right edges. Not every decorative building tile promises identical edge pixels on all four sides. ## Generation and contracts [generateAssets](../scripts/assets.mjs) always bundles the complete named atlas, three animal meshes, and the contact sheet. It does **not** discover named assets by parsing real gameplay declarations or opening named source binaries. Minimal fixture trees containing only mod Lua still package correctly. Existing bounded Lua color scanning remains for compatibility swatches. Those now use subdued earth-tinted clusters, not noisy neon squares; semantic names below are the intended integration target. Every output still passes through `writeAsset`, `relativeName` and `safePath`. Source symlinks/junctions, linked output ancestors and unsafe relative paths remain refused. PNG dimensions/pixels and review-sheet tile count are bounded. No credentials, certificates, insecure cryptography or new downloads were introduced; PNG CRC32 is a file checksum, not a security primitive. This follows the reviewed CodeGuard secret avoidance and bounded/safe-file-handling rules. ### Public pure exports - `worldArt()` returns a fresh ordered `Map`. `data` is straight-alpha RGBA `Uint8Array`; all tiles are 32×32. - `WORLD_NAMES`, `PLANT_NAMES`, `SEASONAL_PLANT_NAMES`, `ITEM_NAMES`, `ANIMAL_NAMES`, `PALETTE` and `SIZE` expose the fixed manifest and palette without accessing the filesystem. - `animalOBJ(species)` returns deterministic original OBJ text; unknown species are rejected. `animalBounds(species)` returns node-unit `min`, `max` and the expected `visualSize`. - `contactSheet()` returns a 320px-wide RGBA review atlas at **full native 32px cell resolution**, not downsampled thumbnails. Height is `ceil(tileCount / 10) × 32` (currently 320px). It accepts 1–160 validated tiles, including every seasonal sprite and both picked-food icons. The 64×32 tree atlases remain separate; their seasonal leaf drawings are reviewed in the corresponding sprite cells. - `artContactSheetPNG()` in the asset generator exports its encoded PNG bytes. `png(width, pixelCallback, height = width)` now supports bounded rectangular output while preserving square callers. ### Output locations Under any generated destination: - `mods/hearth/textures/hearth_.png` - `mods/hearth/textures/hearth_item_.png` - `mods/hearth/textures/hearth_{hare,fox,deer}.png` - `mods/hearth/models/hearth_{hare,fox,deer}.obj` - `mods/hearth/textures/hearth_{hotbar,selected,crosshair}.png` - `mods/hearth/textures/hearth_art_contact_sheet.png` No binary assets need to be checked into the source tree. Normal packaging calls the generator and includes these outputs in its existing integrity manifest. ## Complete named-art manifest / contact-sheet legend The review sheet contains **95 distinct images**, plus the generated sheet itself. There are ten columns, ten rows; rows/columns below are **one-based**. Unused cells in the last row are checker paper. World identifiers use `hearth_` + identifier + `.png`; item identifiers use `hearth_item_` + identifier followed by `.png`. Interface and animal identifiers use `hearth_` + identifier followed by `.png`. | Row | Columns 1 → 10 | | --- | --- | | 1 — world | stone · soil · grass_top · grass_side · sand · log_top · log_side · leaves · planks · wall | | 2 — construction | roof · thatch · foundation · frame · stairs · door · window · vent · storage · pantry | | 3 — plants / clay | reeds · berry_bush · berry_bush_bare · sapling · grain_patch · crop_1 · crop_2 · crop_3 · crop_4 · clay_bank | | 4 — resources / equipment | iron_ore_vein · water · fire · fire_lit · torch · sticks · loose_stone · bedroll · drying_rack · fish_trap | | 5 — ecology | weir_stone · meadow_grass · wildflowers · fern · litter · fungi · deadwood · tree_seedling · young_tree · mature_tree | | 6 — seasonal world | dung · leaves_spring · leaves_autumn · leaves_winter · meadow_grass_dormant · fern_dormant · wildflowers_dormant · spring_greens · spring_greens_dormant · autumn_mushrooms | | 7 — patch, then items | **world:** autumn_mushrooms_dormant · **items:** berries · grain · grain_seed · raw_food · cooked_food · fish_bait · raw_fish · cooked_fish · fish_drying_charge | | 8 — items | dried_fish · iron_ore · iron_ingot · charcoal · charcoal_charge · smelt_charge · clay · clay_jar · clay_jar_charge · dried_food | | 9 — items, then UI | **items:** dried_food_charge · cloak · vessel · water_raw · water_clean · firestarter · iron_axe · spring_greens · autumn_mushrooms · **UI:** hotbar | | 10 — UI / animal UV atlases | selected · crosshair · hare · fox · deer · (five unused cells) | Counts: **61 world/ecology textures, 28 craftitem icons, 3 interface textures, and 3 animal UV atlases: 95 total**. Existing four tool icons, the 64px player skin, color-compatibility textures, original player/tool models and audio remain separate; they are not silently included in this sheet's numbering. ### Alpha and main-agent node integration - `leaves`, `reeds`, `berry_bush`, `berry_bush_bare`, `sapling`, `grain_patch`, all four `crop_*`, `meadow_grass`, `wildflowers`, `fern`, `litter`, `fungi`, `deadwood`, `tree_seedling`, `young_tree`, `mature_tree`, `dung`: crisp 0/255 alpha, zero RGB beneath transparent pixels. Use clip-compatible leaf/glasslike or appropriate plantlike drawtypes; ground litter/deadwood may suit a low mesh or nodebox better. Mature-tree art is a silhouette, **not** a new tree-building algorithm. Ecology controls growth, placement and drops independently. - All craftitems: crisp 0/255 alpha and empty outer borders. Set `inventory_image = "hearth_item_.png"` individually rather than tinting one color square. Processing charges have their own distinct icons. - `window`: empty outside, opaque frame/mullions, alpha-95 glass. Use blend alpha and a suitable glass/nodebox drawtype, not opaque rendering. - `fire`, `fire_lit`, `torch`, `sticks`, `loose_stone`, `bedroll`, `drying_rack`, `fish_trap`: transparent silhouettes. Select clip-compatible rendering or use them as inventory art for physical nodeboxes; an opaque cube will look wrong. Fire is one static drawing, not an animation strip. Emission/light and flame particles remain gameplay decisions. - Remaining world materials and animal atlases: fully opaque. `water` source art is opaque deliberately; liquid transparency must be configured by main. - Six cube face ordering for logs: use `log_top` on the two ends and `log_side` on four sides; grass uses `grass_top`, `soil` bottom and `grass_side` sides. Do not continue multiplying these authored textures by saturated old tints. - `hearth_hotbar.png` is a **single slot tile**, not a prebuilt eight-slot strip. Main must repeat it into its chosen hotbar layout or use individual slot HUD images; directly stretching it across the whole bar would stretch the border. `hearth_selected.png` is a transparent gold/mauve slot outline. `hearth_crosshair.png` has sparse parchment marks and a Mocha contrast outline. Source pixels remain 32px; HUD scale/offset is main's responsibility. ## Original wildlife meshes — integration handoff No external model or flat animal billboard. Hare has deep haunches, grounded hind feet, long upright ears and a small cream tail. Fox has dark slim legs, pointed ears, cream bib/muzzle and a long tapered, pale-tipped brush. Deer has long separated legs, dark hooves, raised neck, broad ears and branched antlers. These are static low-poly models, **not animated B3D rigs**; movement/idle logic must not request player animation frames on them. Select `visual="mesh"`, the species OBJ and the matching PNG texture, with `visual_size={x=1,y=1}`. Mesh coordinates use **10 model units per node**, just like the existing player contract. Feet are Y=0, origin at ground center, forward is **+Z**. Do **not** apply another 10× scale. Each model is one texture, with no external MTL/image references. Eight semantic 8px atlas patches use half-texel inset UVs; stable indexed, outward-wound triangles avoid sampling neighboring material patches. Use nearest-neighbor texture filtering. | Model / matching texture stem | Vertex / triangle count | Min bounds (nodes) | Max bounds (nodes) | | --- | --- | --- | --- | | `hearth_hare` | 112 / 168 | (-0.185, 0, -0.370) | (0.185, 0.848159, 0.429500) | | `hearth_fox` | 128 / 192 | (-0.155, 0, -0.820375) | (0.155, 0.786649, 0.592000) | | `hearth_deer` | 192 / 288 | (-0.280, 0, -0.492526) | (0.280, 1.510000, 0.675000) | `animalBounds()` is authoritative at full precision. These are **visual bounds**, including ears/tails/antlers, not prescribed gameplay collision boxes. Main should choose comfortable body collision/selection boxes, ground-origin spawn offsets and facing/yaw conversion. Review the import orientation in-engine before claiming a verified visual integration. This work does not edit wildlife Lua. ### Preserved player/media contract The original 64×64 player skin, B3D bytes, skeleton, UVs and six animation clips are unchanged. Existing held tools, compatibility OBJ and fifteen Ogg assets are unchanged. The new animals do not reuse or disturb the player's atlas. ## Verification Focused Node-only checks live in [world-art.test.mjs](../tests/world-art.test.mjs), [flora-art.test.mjs](../tests/flora-art.test.mjs) and the unchanged [assets.test.mjs](../tests/assets.test.mjs). No engine tests, live worlds, native client launch or full application test gates are required or claimed by this art task. The tests cover exact named manifests; all 95 distinct byte-deterministic tiles; restricted palettes; color-independent material geometry and contiguous clusters; plant/item occupancy, negative space, alpha and inventory padding; monotonic crop growth; berries versus bare foliage; water seams and sand/clay strata seams; independent PNG signature/chunk/CRC/RGBA decoding; rectangular atlas dimensions and exact unscaled cells; bounded PNG/contact-sheet input rejection; animal topology, finite coordinates, nondegenerate outward triangles, UV patch bounds, counts, species proportions and node-unit bounds; minimal fixture bundling; and linked-output refusal. Existing tests additionally verify deterministic real-source regeneration, fixture package integrity, source traversal limits, player B3D and audio contracts. Validation limitations remain explicit: optional codec decode/regeneration is not run without its configured tool; Ogg CRC/framing/hash checks still run. File symlink creation may be unavailable on Windows; directory-junction refusal is always checked. Geometry tests and the pixel contact sheet are not a claim that animal models have been rendered or that Lua references have been migrated. Historical pre-season baselines were 19 tests for world/media art and 24 with volumetric flora. The seasonal verification section below supersedes those counts. No Lua or other source files are owned by this art task. ## Volumetric ecology trees — refinement The newer ecology trees supersede the billboard recommendation above for `tree_seedling`, `young_tree`, `mature_tree` and `deadwood` only. Original pure generation lives in [flora-art.mjs](../scripts/flora-art.mjs), under the same MIT license. No downloaded vegetation, tree-block construction, external MTL or new dependency is used. Meadow grass, wildflowers, ferns and other ground sprites keep their existing plantlike presentation. `floraOBJ(name)` generates six deterministic OBJ models; `floraBounds(name)` returns their actual serialized vertex bounds in node units. `FLORA_NAMES` is the fixed manifest (four original models plus two bare winter trees). Each leafy standing tree has a tapered eight-sided stem, separate visible boughs and intersecting closed broadleaf lobes: two for the seedling, four for the young tree, five for the mature tree. Three uneven rings and offset tips per lobe produce asymmetric faceted silhouettes from every direction, rather than a giant cube or crossed planes. Deadwood has a low fallen trunk, two broken side branches and a fork. Every face is triangulated with an explicit outward flat normal. Nearest-neighbor filtering preserves the pixel marks on the three-dimensional surfaces. ### Texture and packaging contract `treeAtlas(art = worldArt(), season = 'summer')` returns fresh **64×32 straight-alpha RGBA** data. The left 32px patch is the original `log_side` bark drawing; the right patch is the original `leaves` drawing over opaque `leafDark` underpainting. All alpha bytes are 255 deliberately: spaces between volumetric lobes, not punched-out polygon surfaces, define the canopy. Each triangle stays within its bark/leaf patch with half-texel UV insets. One texture and no `usemtl`/`mtllib` loading are required. [generateAssets](../scripts/assets.mjs) always writes these through the existing guarded `writeAsset`, including when the source has only minimal mod Lua: - `mods/hearth/models/hearth_tree_seedling.obj` - `mods/hearth/models/hearth_young_tree.obj` - `mods/hearth/models/hearth_mature_tree.obj` - `mods/hearth/models/hearth_deadwood.obj` - `mods/hearth/models/hearth_young_tree_winter.obj` - `mods/hearth/models/hearth_mature_tree_winter.obj` - `mods/hearth/textures/hearth_tree_atlas.png` - `mods/hearth/textures/hearth_tree_atlas_spring.png` - `mods/hearth/textures/hearth_tree_atlas_autumn.png` - `mods/hearth/textures/hearth_tree_atlas_winter.png` Normal packaging includes all ten in its integrity manifest. They are generated outputs, not checked-in binaries; the review sheet grows dynamically with sprites. ### Scale, ground and exact visual bounds Coordinates use **10 model units per node**, node-center origin, with ground exactly **Y = -5 model units = -0.5 nodes**. Use `drawtype="mesh"`, `visual_scale=1`, the corresponding OBJ and `tiles={"hearth_tree_atlas.png"}`. No additional scale or vertical offset is needed. | Model stem | Vertices / triangles | Minimum (nodes) | Maximum (nodes) | Height (nodes) | | --- | --- | --- | --- | --- | | `hearth_tree_seedling` | 84 / 152 | (-0.25, -0.5, -0.175) | (0.241, 0.2, 0.1375) | 0.7 | | `hearth_young_tree` | 168 / 304 | (-0.61, -0.5, -0.56) | (0.626, 1.5, 0.45) | 2.0 | | `hearth_mature_tree` | 210 / 380 | (-1.04, -0.5, -1.04) | (1.064, 3.0, 1.064) | 3.5 | | `hearth_deadwood` | 64 / 112 | (-0.4044987, -0.5, -0.8019998) | (0.4034683, -0.0422183, 0.8016249) | 0.4577817 | These are visual bounds, not canopy-sized obstacles. The ecology registration loop alone selects mesh rendering and disallows replacement for all four. Young and mature trees are walkable with narrow trunk collision/selection boxes: (-0.12, -0.5, -0.12) to (0.12, 0.6, 0.12) for young trees and to (0.12, 1.2, 0.12) for mature trees. Deadwood has a low central-trunk box; seedlings remain nonwalkable. The semantic-art registration helper preserves explicit `def.tiles`; the loop supplies its mesh atlas directly. No `override_item` API or mock is required. Growth clearance, succession logic and canopy obstruction policy remain outside this art change; no changes were made to the succession module. ## Seasonal flora — exact integration contract The authoritative runtime declarations are [seasons.lua](../mods/hearth/seasons.lua) and [content.lua](../mods/hearth/content.lua), documented in [ecosystems.md](ecosystems.md). All following files are generated into the destination's texture/model directories, never written into source by packaging. `TREE_SEASONS` exports `summer`, `spring`, `autumn`, `winter`; unsupported values fail closed. Summer's atlas pixels and all four original meshes are unchanged. ### Trees and legacy leaf nodes | Season | Mesh-tree texture | Legacy leaf-node texture | Young / mature mesh | | --- | --- | --- | --- | | Spring | `hearth_tree_atlas_spring.png` | `hearth_leaves_spring.png` | Existing leafy meshes | | Summer | `hearth_tree_atlas.png` | `hearth_leaves.png` | Existing leafy meshes | | Autumn | `hearth_tree_atlas_autumn.png` | `hearth_leaves_autumn.png` | Existing leafy meshes | | Winter | `hearth_tree_atlas_winter.png` | `hearth_leaves_winter.png` | `hearth_young_tree_winter.obj`, `hearth_mature_tree_winter.obj` | Every atlas is **64×32, fully opaque RGBA**: unchanged bark left, seasonal botanical clusters right. Spring uses fresh sage with sparse dusty buds; Summer retains deep sage; Autumn uses copper, ochre and muted wine with **no green**. Winter's right patch contains warm woody twigs over bark-dark underpaint, not green or snow-painted foliage. Legacy 32×32 leaf textures retain crisp 0/255 alpha; Winter is sparse interlacing twig forks with transparent negative space. Both winter meshes contain **only trunk, boughs and lateral woody forks**. Every original trunk/bough vertex, UV, normal and triangle is retained exactly; crowns are omitted and each original bough gains one outward lateral fork. They stay within the original leafy clearance envelopes, at ground Y=-0.5 nodes, scale 1, 10 model units/node. Every winter triangle samples only the left bark half. | Model | Vertices | Triangles | Leafy triangles | | --- | --- | --- | --- | | Young winter | 112 | 196 | 304 | | Mature winter | 144 | 252 | 380 | Seedlings intentionally reuse `hearth_tree_seedling.obj` in **all seasons**, with the corresponding atlas, exactly as runtime requests. There is no unreferenced winter seedling mesh. Deadwood remains on its original mesh and summer atlas. No additional summer variant, snow overlay or tree-block construction is added. ### Ground flora and seasonal food All sprites/icons below are **32×32 straight-alpha RGBA**, with crisp 0/255 alpha and zero hidden RGB. Picked-item icons additionally have empty outer borders. Dormant flora uses straw/bark/earth, **no green and no blooms**, and has reduced, visibly different silhouettes rather than a palette-only recolour. | Runtime node(s) | Generated texture(s) | Appearance / availability | | --- | --- | --- | | `meadow_grass_winter` | `hearth_meadow_grass_dormant.png` | Bent dry grass blades in Winter; original living art otherwise | | `fern_winter` | `hearth_fern_dormant.png` | Curled spent fronds in Winter; original living art otherwise | | `wildflowers_autumn`, `wildflowers_winter` | `hearth_wildflowers_dormant.png` | Spent stalks/seed heads, no bloom; original bloom in Spring/Summer | | `spring_greens_patch` | `hearth_spring_greens_dormant.png` | Low dry rosette outside Spring | | `spring_greens_patch_spring` | `hearth_spring_greens.png` | Broad fresh basal blades with veins; available only in Spring | | `autumn_mushrooms_patch` | `hearth_autumn_mushrooms_dormant.png` | Low litter/mycelial trace without pickable caps outside Autumn | | `autumn_mushrooms_patch_autumn` | `hearth_autumn_mushrooms.png` | Three staggered copper/ochre caps and pale stalks; available only in Autumn | Picked craftitems `hearth:spring_greens` and `hearth:autumn_mushrooms` use `hearth_item_spring_greens.png` and `hearth_item_autumn_mushrooms.png` respectively: a bound greens bundle and gathered mushrooms. **Patch node identifiers are not texture stems**: no `_patch.png` aliases are generated. Existing `fungi` remains visually distinct decomposer art, not seasonal edible forage. These are original fictional food drawings, not real-world plant/mushroom identification guidance. The generator adds **15 PNGs and two OBJs**: three seasonal atlases, three leaf textures, three dormant ground textures, four forage patch textures, two item icons and two winter meshes. All names are bundled even from minimal Lua-only fixture sources and included in the existing SHA256 package integrity inventory. No Lua callbacks, harvest cooldowns, seasonal authority, metadata or placement rules are changed. There are no new dependencies, downloads, credentials or external material paths; existing bounded safe-path writers are retained. ### Seasonal verification [flora-art.test.mjs](../tests/flora-art.test.mjs) checks all six models with the unchanged finite/indexed/nondegenerate/outward-normal, positive-volume and closed oriented-manifold-per-part validations. Added checks cover wood-only winter UVs, fewer triangles, outward lateral forks, original trunk/bough geometry preservation, clearance envelopes, all four opaque atlases and byte-exact bark. Generated PNG bytes, exact OBJ text and complete package integrity are verified from fixtures. The current Lua explicit-tile wiring and separate patch/item stem contract are read-only assertions, not a Lua execution or engine test. [world-art.test.mjs](../tests/world-art.test.mjs) checks the exact 95-image manifest, determinism/uniqueness, restrained palettes, alpha/masks/padding, Spring buds, non-green Autumn leaves, bare Winter twigs, bloom-free dormancy, forage/decomposer silhouette differences, dynamic sheet dimensions and every seasonal sprite cell. Existing material, animal, PNG and safe-output tests stay. Run all three focused asset suites, including the unchanged [assets.test.mjs](../tests/assets.test.mjs), with the absolute Node 24.22.0 executable. Optional codec regeneration is not required: existing Ogg framing/CRC/hash tests remain active. These checks validate pixels, topology and packaging, **not an in-engine seasonal visual acceptance pass**. No full engine tests or concurrent engine launches are part of this art task. Latest seasonal run: **27 passed, 0 failed, 0 cancelled, 0 skipped**, exit 0, empty stderr, **37.37 seconds**, using the absolute Windows Node 24.22.0 executable. All five owned JavaScript/test modules passed `node --check` with exit 0 and empty stderr. The 95-image **320×320** generated contact sheet was inspected separately. Optional codec decode/regeneration was not configured; Ogg framing/CRC/hash checks passed. Windows file-symlink privilege was unavailable; directory-junction refusal passed (the suite reported no skipped tests).