Coheronia - Variable Matrix
State: audited 2026-07-16 against the native HUD-kit stabilization, authored opening-cel integration, and 120-file body-specific player-gear program.
Authority Surfaces
| Surface | Authority | Consumed by |
|---|---|---|
| Blocks | data/blocks.json | BlockRegistry, world, player, settlement_model, hud |
| Recipes | data/recipes.json | player, town_hall |
| Craft stations (FQ-11) | data/recipes.json stations (workbench/furnace/anvil: prereq + build_cost) + station recipes (station, output_to, equip_slots) | BlockRegistry.station_defs/station_def/recipes_for_station; town_hall.build_station (spends stockpile, prereq-gated, stations_built saved in to_dict) and town_hall.craft_station (inputs from stockpile; smelted ingots output_to stockpile, anvil gear equips with empty-slot+fit guard before consuming); hud rebuilds the town-panel station section from data each refresh; metal gear is gated ore -> furnace ingot -> anvil (validator-enforced: no anvil recipe consumes raw ore) |
| Farming (FQ-12) | farm_soil/crop_seedling/crop_ripe blocks in data/blocks.json (crops requires_support, non-solid, non-placeable; crop_ripe drops food + a seed) + craft_seeds recipe (data/recipes.json, food -> seeds) | player.try_farm (one key farm_action/G: tills dirt/grass via world.till_soil, plants a seed on farm_soil via world.plant_crop, consuming one crop_seeds); world._tick_crop_growth ripens seedling -> ripe after CROP_GROW_SECONDS on tilled soil, removes unsupported crops (never floats, never regrows into invalid cells), timers in crop_growth persist via serialize_crop_growth/parse_crop_growth in save_manager; world.farm_tile_count() -> game_root.summary() food-yard score |
| Map / scouting (FQ-15) | discovered bands in scripts/world/map_state.gd (coarse REGION=16 grid; no JSON authority); the scouting hook is the explorer biome_reveal perk map_discovery_speed in data/progression/perks.json | game_root._process reveals bands around the player (_scout_reveal_radius reads the perk); map_snapshot builds hall/player/ore/threat markers over revealed bands only; hud/scripts/ui/map_panel.gd draw it (M toggles); map_revealed_serialized/apply_map_revealed persist the compact band list via save_manager (map_revealed) |
| Goal panel (FQ-14) | objective list in scripts/main/goal_tracker.gd GOALS (id/text/hint) — no JSON authority | game_root._goal_snapshot derives per-objective booleans from live state (inventory wood/stone, cached light_score, town_hall.total_stock, tool_tier/axe_tier/stations_built, day_count); goal_tracker.note prefix-latches and current() feeds hud.update_goal; toggle_goals (O) hides the panel. Goal progress remains presentation-only and is re-derived on load; panel visibility is a profile-level HUD preference. |
| Settlement formulas | data/settlement_rules.json | settlement_model.gd via Godot Expression |
| World settings | data/world_settings.json | WorldConfig, shell UI, world generation, gameplay systems |
| Character data | data/character_data.json | shell UI, player.apply_character, role item grant |
| Player cosmetic variant (FQ-13P3 + art run) | character record visual_variant int (shell.json; game_state.create_character, default_visual_variant legacy default; NOT in world saves) | player.visual_variant -> player_visual._select_body_texture picks art/generated/players/<body_id>_NN.png (0 = canonical, k>0 = pool[k-1] wrapped, else canonical); all ten body ids have _01/_02; shell_ui._refresh_look_range exposes only the real pool length |
| Equipment (FQ-03) | data/equipment.json | BlockRegistry equipment helpers -> player gear API, hud panel, game_root carried-state load, save_manager |
| Legacy UI hooks (FQ-13P2/FQ-19) | art/generated/ui/<id>.png (15 reserved ids generated by scripts/gen_ui_placeholders.py; convention-picked, no manifest entry) | Older slot/orb/button consumers and code-drawn fallbacks; retained as resilience paths when the primary HUD kit is incomplete |
| Primary HUD dock kit (2026-07-16 stabilization, contract v2) | 19 native-size RGBA assets plus hud_dock_layout.json in art/source_templates/hud_dock/, promoted one-for-one to art/generated/ui_painted/ by scripts/art/sync_hud_kit.py | hud._load_hud_kit_layout/_hud_kit_available select the native kit first; JSON supplies every dock/vessel/fill/label/slot/button rectangle plus inner icon/count/hotkey/visible-label rectangles, alpha rules, state families, trim keep-outs, and manifest-driven non-interactive decorative layers. Runtime children own health, attunement, icons, counts, hotkeys, selection, labels, actions, and FX. New interactive controls still require a registered action. The FQ-21 sliced band and FQ-19 modular dock remain fallbacks. |
| Movable HUD modules | profile hud_layout schema v5 (crest, goal, events, map, modules; dock is transform-invariant) | hud.gd move/resize/edit controls; Map and Events use independent non-overlapping defaults and may remain visible together; older geometry schemas reset to current defaults |
| Visual assets (FQ-07 + 2026-07-14 art run) | data/visual_assets.json + art/generated/<category>/<id>.png convention; scripts/asset_audit.py derives rendered block/item ids plus every live enemy and live enemy drop | BlockRegistry.visual_texture -> world._make_block_texture (blocks), simple_threat._draw (enemies), hud toolbelt/grids (items via item_icon); all current rendered block/item/live-enemy ids have canonical PNGs, while missing future images still fall back; loaded via Image.load_from_file; explicit json entries validator-fail when broken, convention gaps are INFO-only |
| Variant pools (FQ-09V + art run) | <id>_01.png … convention (consecutive, max 8) or explicit arrays in data/visual_assets.json; current pools: 17 blocks × 3, 6 enemies × 3, 10 player bodies × 2 | BlockRegistry.visual_variant_textures -> blocks via deterministic seed+cell choice, enemies via lifetime-stable spawn hash, players via character-owned Look index; empty pool = canonical/fallback path; asset_audit.py --strict fails gaps, more than eight files, unreadable PNGs, and wrong dimensions |
| Pixel-art normalization | reviewed image-model source staged outside the repo; scripts/art/prepare_pixel_asset.py crops/scales, palette-reduces, hardens alpha, and seam-locks opaque tiles after the installed imagegen chroma helper handles cutouts | exact target PNGs under art/generated/; contact-sheet review plus strict audit/validator/runtime smoke gate promotion; source generations remain outside the public repo |
| Item metadata (FQ-09) | data/items.json | BlockRegistry.display_name fallback chain (blocks -> items.json -> id), item_description (tooltips), item_fallback_color -> item_icon swatches for the FQ-09 icon grids; unknown ids get a stable hash-derived hue |
| Ore families (FQ-10) | ore-family blocks in data/blocks.json (coal/copper_ore/tin_ore/iron_ore/silver_ore/crystal — each drops itself, pick-preferred, tier 1 or 2) + data/world_settings.json ore_table (per-family min/max depth, frequency, threshold, unique seed_offset; validator-enforced) | WorldGen._build_ore_families/_ore_family_at place families by depth band on independent noise channels, claiming only cells that would be stone (the generic ore vein is untouched); ore_abundance scales all thresholds and 0 disables every ore; deterministic per seed+cell, never saved; every family now has canonical block/item art and three block variants, with fallback colors retained for resilience |
| Enemies | data/enemies.json (FQ-13: six live enemies; per-def contact_damage/speed/hp, optional hp_mult/hall_dps_mult/targets_crops/spawn_rule) | enemy_registry.gd (live_defs/get_def) -> game_root spawn paths (_spawn_enemy_at applies hp_mult/hall_dps_mult/targets_crops; _maybe_spawn_thornrat/_maybe_spawn_torchbearer; _advance_cave_spawns picks ore_tick when world.has_ore_within), simple_threat (targets_crops -> world.nearest_crop/world.eat_crop crop harasser; FQ-13P1 _select_sprite picks one enemies variant pool sprite per instance at creation via variant_for(id, spawn_cell, world_seed, pool_size) = posmod(hash("id:cell:seed"), n), fixed for life, recomputed on load so never saved; canonical -> family-tint fallback; drops) |
| Ancestries | data/ancestries.json | ancestry_registry.gd -> player.apply_ancestry_effects, shell create form |
| Progression | data/progression/*.json | progression_registry.gd -> XP awards, base levels, HUD |
| Shell profile + character-carried state | user://shell.json | GameState, game_root carried-state load/apply |
| World files | user://worlds/<id>.json | GameState, SaveManager, shell world list |
| Shell UI help text | data/world_settings.json ui_help | shell create screen (axis/gen/preset help) |
| Ancestry detail text | scripts/data/ancestry_detail.gd build_panel_text | shell create form, smoke |
| Opening cinematic narrative (FQ-09C) | scripts/shell/prologue.gd SCENES (id, phase, duration, overlay text, audio cue, animation cues; exact copy locked by docs/OPENING_STORYBOARD.md) | prologue controller tick clock/text/audio, smoke |
| Opening cinematic imagery (FQ-09C + authored cels) | scripts/shell/prologue_canvas.gd build_commands(scene, tick) remains the deterministic 640x360 fallback; authored pools live under art/generated/opening/ | prologue.gd prefers authored scene pools when present and returns to the plotted SubViewport path when absent; smoke fingerprints both contracts |
| Opening puppet acting (FQ-09C) | scripts/shell/prologue_puppets.gd — static articulated-figure renderer + keyframe tracks (angles quantized to 5°, pixels snapped) | prologue_canvas.gd scene functions |
| Opening cel-shot hook (FQ-09C) | BlockRegistry.visual_variant_textures("opening", scene_id) — FQ-09V <id>_01.png convention or explicit array in data/visual_assets.json; current coverage is eight scene pools / ten PNGs (three frames for opening_01_first_star, one for each remaining scene); empty pool = plotted fallback | prologue.gd (pool loops at 8 fps for the scene; one frame is static) |
| Action effects (FQ-09M) | scripts/fx/action_fx.gd — five deterministic self-freeing kinds, stepped 10 Hz, "action_fx" group, spawn(parent, kind, at[, tint]) | player.gd (place/cast/hurt/respawn), simple_threat.gd (hit/death), game_root._craft_confirm_fx (forges + hand craft); transient, never saved |
| Tool swing (FQ-09M) | player.swing_phase() — 0/1/2 six pose-steps/s from mining progress, -1 idle; drawn in player._draw with pick/axe glyph | presentation only; mining timing/drops pinned by unchanged baselines |
| Adaptive music runtime (FQ-09U1) | data/music_manifest.json via scripts/audio/music_manifest.gd -> scripts/audio/adaptive_music_director.gd (scenes/audio/AdaptiveMusicDirector.tscn in Main); "Music" bus created at runtime | one AudioStreamInteractive, four named context clips, any->clip next-bar same-position crossfades (crisis entry next-beat); reads is_night/storm_active/current_threat_severity/health ratio/cave-spawn underground rule + the settlement updated signal; transient — never saved |
| Music context state machine (FQ-09U1) | adaptive_music_director.evaluate(state, delta) — deterministic; priority crisis > underground > surface_night > surface_day; pressure = max(threat/40, load/100, 1-health)+storm 0.15; hysteresis 0.60/2s enter, 0.35/6s exit; 1-bar min hold; no re-requests | 0.5 s poll in _process; smoke drives it directly with synthetic snapshots |
| Stem layer bed (FQ-09U2) | music_manifest.json stems + stem_mix (per-stem source/min_db/max_db, smoothing_db_per_sec 6, storm_pressure_floor_db -16; validator-enforced) -> MusicManifest.load_stem_streams -> AudioStreamSynchronized on the LayerPlayer, same-frame start with the context stream (phase-locked by construction), loop lengths runtime-validated to 53.333 s ±0.05 | volumes lerp toward targets from settlement resilience/coherence (cached updated signal), pressure score (+storm floor), player attunement ratio, mining/movement activity, collapse edge (pressure>0.7); any failure disables layering only — context music unaffected; transient, never saved |
| Event stingers + ducking (FQ-09U3) | music_manifest.json stingers (five kinds: dawn/nightfall/raid_warning/attunement/base_advance) + stinger_config (duck_db -9, attack/release dB per sec, cooldown_seconds 8) -> MusicManifest.load_stinger_streams -> adaptive_music_director.play_stinger(kind) on the SFX-bus StingerPlayer with a per-frame Music-bus duck envelope | wired (deferred) to game_root.music_event(kind) + player.attunement_pulsed; per-kind cooldowns; the bed dips under the stinger, music never stops; transient — never saved |
| Audio settings (FQ-09U3) | scripts/audio/audio_settings.gd — the single bus-volume authority; apply(profile[, duck_db]) creates the Music + SFX buses at runtime and sets volumes from profile keys music_volume/sfx_volume (linear); set_music_volume/set_sfx_volume persist to the profile | shell title Music/Sound sliders and the director; profile-level preference, never a world-save key; director process_mode = ALWAYS keeps score + duck ticking under pause |
| Player body art + rig (art integration) | data/player_visuals.json (16x32 bodies, default/female variants, per-species skin palettes + regions, default_rig, gear/swing overlay conventions) + data/character_data.json appearances (skin/trim palettes) -> scripts/player/player_visual.gd | PlayerVisual loads a canonical plus two authored Looks for every current body and applies constrained appearance recolor. Authored coverage is 30 static crude-armor overlays (helmet/torso/feet across ten bodies) plus 90 three-phase tool overlays (basic pick, forged pick, crude axe across ten bodies); resolution remains body-specific -> generic -> rig-aware procedural fallback. Intermittent load/refresh alignment is a known visual defect. |
| Town Hall + world-anchor art (art integration) | art/generated/structures/town_hall.png (56x48) + art/generated/blocks/town_hall_core.png (16x16) + art/generated/backgrounds/* strips, all nearest-filtered | scripts/settlement/town_hall.gd draws the Hall at Rect2(-28,-48,56,48) with the procedural Hall as fallback (damage overlay preserved); scripts/world/world_backdrop.gd uses native strip sizes with Camera2D zoom as the sole 2x scale; validator art contracts (SHA-checked sizes) |
| Scenic backdrop (FQ-09W) | scripts/world/world_backdrop.gd — code-drawn sky/far/mid planes, stepped parallax, light_mask 0; optional art/generated/backgrounds/ hooks (surface_sky, surface_far_terrain, surface_mid_silhouette) | world scene (z -10); cosmetic only, never saved/collides |
| Natural backing walls (FQ-09W) | derived each world.setup from pristine gen.surface + generation.dirt_depth — dirt_wall band then stone_wall; tileset has 0 physics / 0 occlusion layers; optional art/generated/back_walls/ tiles else darkened block textures | BackgroundWalls TileMapLayer (z -2); world.wall_at(cell) visual-only query; never in cells/deltas/saves |
| Column skylight + cave ambient (FQ-09W) | world.sky_line(x) (first solid y in LIVE column, cached, invalidated per column on block change) -> game_root.ambient_darkness_factor() / ambient_target_color() (CAVE_TINT, CAVE_FADE_CELLS 6) | the CanvasModulate tint lerp in _advance_time and the instant set in apply_time_state; documented approximation — player-column roof-awareness, no lateral bleed |
| Title/authorship/tagline text (FQ-09C) | prologue.gd consts TITLE_TEXT / AUTHORSHIP_TEXT / TAGLINE_TEXT | cinematic title card and shell_ui._show_title (single source, cannot drift) |
profile.prologue_seen (FQ-09C) | user://shell.json profile via GameState.mark_prologue_seen() (idempotent; written only on completion or skip) | shell_ui._ready clean-profile autoplay gate |
All registries load through scripts/data/json_data.gd (load_dict, push_error + {} on failure).
Current Presentation Defects
| Surface | Current truth | Next verification target |
|---|---|---|
| Equipped character art | Equipment state/effects persist, but a matching body-specific overlay can intermittently fail to appear or align after some character/load transitions. The rig-aware fallback prevents a gameplay failure. | Reproduce across all ten body ids, save/load, world transitions, and newly forged gear; assert overlay resolution and force a visual refresh at the authoritative state boundary. |
| Tool and weapon swing | Basic/forged pick and crude axe have three authored phases per body. They currently snap between poses; anchors, mirroring, timing, and arc continuity need polish. The sword has no equivalent authored attack sequence. | Capture left/right swings for every body at all phases, then add a sword sequence without changing mining/combat timing. |
| HUD chrome and framed panels | The native JSON dock contract resolves the previous dock overlap/scaling conflict, but the current PNGs are provisional. Some framed panel/capture states still show padding, mask, or oversized opaque-region defects. | Replace chrome through docs/wiki/hud_asset_replacement_studio.md, then visually inspect every panel at target window sizes in addition to smoke assertions. |
Planned Authority Surfaces (Not Live)
These rows record operator-approved planning authority. (The FQ-09C opening cinematic and the FQ-09W backdrop/backing-wall/ambient planes are live — see the authority table above; docs/ART_DIRECTION_AND_CANON.md, docs/OPENING_STORYBOARD.md, and art/source_templates/BACKGROUND_TEMPLATE.md remain the style/copy/asset authorities for them and for later art.)
| Planned surface | Planning authority | Intended consumer |
|---|---|---|
| Canon, tone, terminology, palette roles | docs/ART_DIRECTION_AND_CANON.md | asset prompt packs, later public/game writing |
| Asset map: every live/planned id, path, size, priority, prompt pack | docs/ASSET_ROADMAP.md (FQ-09A; validator phrase-locked) | human/LLM art passes; no machine manifest exists by decision |
| Adaptive music program: contexts/stems/stingers, state model, hysteresis, gating | docs/WORK_ORDER_FQ_09U_ADAPTIVE_MUSIC.md + audio/source_templates/MUSIC_TEMPLATE.md (FQ-09U0; operator-approved) | Codex (Godot spike, M8str0 M8-AUDIO-01..03), music-authoring LLM, FQ-09U1..U3 implementation |
| Music machine contract: grid, contexts, priority, transitions, pressure norms, thresholds | data/music_manifest.json (validator-checked: 72/4/16 grid, 4 contexts, crisis hysteresis) | Live scripts/audio/music_manifest.gd -> scripts/audio/adaptive_music_director.gd; smoke exercises context selection, transition timing, stems, stingers, and ducking |
| Constructed (player-placeable) backing walls | art/source_templates/BACKGROUND_TEMPLATE.md | a later bounded gameplay task — needs wall deltas, drops, recipes, save ownership/migration decisions |
Cave/deep background layers (cave_far, deep_cavern_far) and wall variants beyond dirt/stone | art/source_templates/BACKGROUND_TEMPLATE.md | FQ-09A asset roadmap; runtime wiring when those environments exist |
Scenic backdrops, natural backing walls, and foreground cells are separate planes (live as of FQ-09W). No wall dictionary or wall delta is a live save authority: natural walls are deterministic/derived; player-placeable walls require a later explicit save ownership and migration decision.
v0.6 Ownership Boundary
| State | Owner | Notes |
|---|---|---|
| Inventory counts, hotbar slot, tool tiers {pick, axe} | character (user://shell.json) | carried_inventory, carried_slot, carried_tool_tiers, legacy carried_tool_tier alias |
| Gear slots (FQ-03/FQ-04/FQ-11) | character (user://shell.json) | equipment dict (12 slot_id -> item_id, "" = empty); pickaxe/axe slots derive from live tool tiers at save time; weapon and crude/iron armor acquisition and effects are live; rings, amulet, and accessory persist even where gameplay effects or authored presentation remain limited; pre-FQ-03 characters gain the dict on migration |
| Role starter items | character | items_granted flag; granted once per character |
| Terrain, hall stockpile, time, threats, storms, settlement | world file | unchanged |
| Player position, health | world file | entering character inherits world position/health |
| XP totals, player level, base level/XP | world file | explicit v0.6 decision; revisit for cross-world leveling |
| Legacy migration | one-time | character without carried_inventory adopts world save's player keys once; legacy tool tier -> {pick: N, axe: 0} |
World Config Matrix
| Config key | Consumed by | Effect |
|---|---|---|
size | WorldConfig.size_dims, WorldGen.generate, world.world_bounds, camera limits | small 160x64, medium 240x80, large 360x100 |
seed | WorldGen.generate | deterministic base terrain |
generation.terrain_amplitude | WorldGen.generate | surface height variation |
generation.terrain_frequency | WorldGen.generate | surface noise frequency |
generation.dirt_depth | WorldGen.generate | dirt layer depth before stone/ore |
generation.ore_abundance | WorldGen.generate | ore threshold; 0 disables ore |
generation.ore_seed_offset | WorldGen.generate | independent ore layout channel |
generation.tree_density | WorldGen.generate | tree site frequency; 0 disables trees. FQ-09R: every site grows one unified tree (tree_trunk column + tree_leaves canopy), walk-past and harvestable. The FQ-02 tree_foreground_ratio key/slider is removed (stored copies in old world configs are ignored) |
generation.tree_seed_offset | WorldGen.generate | independent tree channel |
generation.bush_density | WorldGen.generate | berry bush frequency; 0 disables bushes |
generation.bush_seed_offset | WorldGen.generate | independent bush channel |
difficulty.enemy | night_spawn_count, threat_hp, threat hall_dps | night threat count, hp, hall damage |
difficulty.ruler | settlement_model.gather_inputs | population pressure multiplier |
difficulty.survival | daily_food_need | food demand multiplier |
difficulty.economy | settlement_model.gather_inputs | stockpile/scarcity scaling |
difficulty.social | stored only | reserved for social simulation |
difficulty.impressionability | growth_threshold | lower threshold means easier population growth |
environment_danger | storm roll and storm dps | storm frequency/damage multiplier |
rules.subjects_require_food | food consumption, starvation, scarcity, growth gate | disables/enables food pressure |
rules.weather_affects_survival | storm roll and force_storm | disables/enables storms |
rules.lighting_affects_safety | night_spawn_count | torch light can reduce night spawns |
rules.darkness_increases_enemies | night spawns and threat severity | disables/enables night threats |
rules.enemies_scale_over_time | threat_hp | threat hp grows with day count |
| future rule toggles | persisted only | sleep, sickness, morale, loyalty, rebellion, ruler pressure growth, scarcity growth |
Character Matrix
| Character key | Authority | Consumed by | Effect |
|---|---|---|---|
name | shell create form / shell.json | shell lists | display identity |
species | character_data.json + ancestries.json | shell create form, game_root.apply_ancestry_for_species | live phase B ancestries: human, dwarf, elf, goblin, orc; effects re-derived from data at world entry |
appearance | character_data.json | player.apply_character | body/trim draw colors |
traits | character_data.json | player.apply_character | max health, mining speed, reach, food bonus, growth delta |
role | character_data.json | role grant and player.apply_character | starting items and role effects |
FQ-01 Player Defaults (Health, Healing, Collapse)
| Variable | File / Field | Default | Effect |
|---|---|---|---|
base_max_health | data/character_data.json player_defaults.base_max_health | 100 | base max health before trait/role/ancestry bonuses (player._base_max_health) |
hurt_cooldown_sec | data/character_data.json player_defaults.hurt_cooldown_sec | 0.8 | i-frame window after take_damage; blocks re-damage until it expires |
food_heal_amount | data/character_data.json player_defaults.food_heal_amount | 25 | health restored per food eaten (clamped to max_health) |
eat_cooldown_sec | data/character_data.json player_defaults.eat_cooldown_sec | 0.5 | minimum time between eat_food actions |
passive_regen_per_sec | data/character_data.json player_defaults.passive_regen_per_sec | 1.0 | health/sec regenerated near the Town Hall when safe |
safe_radius_px | data/character_data.json player_defaults.safe_radius_px | 160 | distance from hall center within which passive regen can trigger |
collapse_loss_fraction | data/character_data.json player_defaults.collapse_loss_fraction | 0.25 | fraction (floored per stack) of carried inventory lost on collapse (health reaching 0) |
low_health_fraction | data/character_data.json player_defaults.low_health_fraction | 0.25 | health/max_health ratio below which the HUD tints red and logs "You are badly hurt." once per crossing |
All eight keys are read once via player._load_player_defaults() from BlockRegistry.character_data["player_defaults"] (same JSON-load pattern as traits/roles/appearances); missing keys fall back to the DEFAULT_* consts in player.gd.
FQ-05 Attunement (Player Magic Resource)
| Variable | File / Field | Default | Effect |
|---|---|---|---|
base_max_attunement | player_defaults.base_max_attunement | 50 | base of the computed player.max_attunement() |
attunement_regen_per_sec | player_defaults.attunement_regen_per_sec | 2.0 | constant regen everywhere (no safety gate), scaled by ancestry attunement_regen_mult |
attunement_pulse_cost | player_defaults.attunement_pulse_cost | 15 | attunement spent per light pulse (attune_pulse action, R) |
attunement_pulse_cooldown_sec | player_defaults.attunement_pulse_cooldown_sec | 1.0 | pulse re-cast gate |
attunement_pulse_duration_sec | player_defaults.attunement_pulse_duration_sec | 4.0 | pulse light fade time |
player.attunement | player.gd / world save (player.attunement) | full | current pool; world-saved next to health; pre-FQ-05 saves default to full |
player.max_attunement() | player.gd (computed) | 50 | base + ancestry attunement_bonus + gear attunement_bonus sum; perks join here at FQ-06 |
attunement_bonus / attunement_regen_mult | data/ancestries.json player_effects (hooks; no live ancestry sets them yet) + data/equipment.json item effects (amulet_focus = +10) | 0 / 1.0 | ancestry additive max + regen multiplier; gear additive max |
Extension points documented in docs/FUTURE_PROGRESSION_RESEARCH_AND_BASE_LEVELS.md ("Attunement Extension Points"). A non-magic character never spends or needs attunement; nothing existing is gated by it.
Health, Healing, And Collapse
Two healing sources are wired in FQ-01: eat food (active, bound to the eat_food input action — key H — consumes 1 food from the inventory for an instant food_heal_amount heal, no-op at full health or during its own cooldown) and safe passive regen (near the Town Hall center within safe_radius_px and clear of any "threats" group node within 200px, health trickles back at passive_regen_per_sec; logs "You feel safe near the Town Hall and begin to recover." once per regen streak). Damage still gates through the existing hurt_cooldown_sec i-frame window and flashes the player sprite red for ~0.2s. Reaching 0 health triggers a collapse: each carried inventory stack loses floor(count * collapse_loss_fraction) (deterministic, per stack) before the player respawns at the Town Hall at full health, with the log message "You collapsed and awoke near the Town Hall. Some of your supplies were lost." Crossing below low_health_fraction of max_health logs "You are badly hurt." once per crossing and tints the HUD health bar/label; it resets when health rises back above the threshold.
Core Gameplay Variables
| Variable | Type | Authority | Notes |
|---|---|---|---|
tile_size | int | data/blocks.json | 16 px |
block_id | string | data/blocks.json | dirt, grass, wood, stone, ore, tree_trunk, tree_leaves, berry_bush, torch, lantern, town_hall_core, air |
tree_trunk / tree_leaves | blocks | data/blocks.json | FQ-09R unified trees: both non-solid (walk in front of/past, no occlusion) and non-placeable; trunk = wood hardness 0.55, axe-preferred, drops 1 wood; leaves = hardness 0.15, no drops. Generated into cells like any block; mined cells persist as normal air deltas |
hardness | float | data/blocks.json | mining time input |
required_tool_tier | int | data/blocks.json | ore requires tier 2 (pick tier) |
preferred_tool | string | data/blocks.json | axe: wood, berry_bush; pick: stone, ore; axe-preferred mine 1.4x faster with an axe |
requires_support | bool | data/blocks.json | berry_bush; enforced on mine, load sweep, and regrowth |
axe_tier | int | player.gd / character-carried | 0 = no axe; craft_axe recipe (4 wood + 2 stone, town_hall) sets 1 |
equipment (slots) | data | data/equipment.json | FQ-03: 12 slots (weapon, axe, pickaxe, helmet, torso, feet, ring_1-4, amulet, accessory) with accepts slot_type; validator-enforced |
equipment (items) | data | data/equipment.json | item defs {display_name, slot_type, description, effects}; live: pick_basic (pick_tier 1), pick_forged (pick_tier 2), axe_crude (axe_tier 1), sword_crude (attack_damage 3), helmet_crude/torso_crude/feet_crude (armor 1/2/1); inert: ring_band |
attack_damage | int effect | data/equipment.json -> player.attack_damage() | FQ-04: melee damage per hit from the equipped weapon; 1 bare-handed; consumed by _try_hit_threat -> threat.take_hit |
armor | int effect | data/equipment.json -> player.armor_total() | FQ-04: flat incoming-damage reduction summed over ALL equipped items (helmet/torso/feet today); applied in take_damage with a minimum 1-health chip per landed hit |
craft_sword / craft_armor_set | recipes | data/recipes.json | FQ-04: town_hall station, empty outputs; consumed by town_hall.forge_sword/forge_armor which equip gear via player.equip_item (guards: weapon/torso slot must be empty) |
player.equipment | dictionary | player.gd / character-carried | slot_id -> item_id for the 10 non-tool slots; equipped_dict() merges in pickaxe/axe derived from live tiers; equip_item(slot, item) validates fit via BlockRegistry.item_fits_slot; tool slots cannot be cleared (tiers come from forging) |
drops | dictionary | data/blocks.json | inventory additions on break |
is_placeable | bool | data/blocks.json | placement gate |
is_solid | bool | data/blocks.json | collision, shelter, roof, defense |
blocks_light | bool | data/blocks.json | runtime occluder polygons |
emits_light | bool | data/blocks.json | runtime PointLight2D nodes |
light_radius | int | data/blocks.json | torch/lantern radius |
settlement_tags | array | data/blocks.json | protected, defense |
world_seed | int | world file state | deterministic terrain regen |
terrain_deltas | dictionary | world file state | edits over regenerated terrain |
world.width/height | int | WorldConfig.size_dims | replaces old constants |
surface | dictionary | WorldGen.generate | top solid y per x |
hall_info | dictionary | WorldGen.stamp_town_hall | center cell, ground y, protected core cells |
bush_regrow | dictionary | world.gd / save | harvested bush timers |
player_position | Vector2 | save state | restored after world rebuild |
player_health | float | player.gd / save | HUD and threat damage |
player.max_health | float | player.gd | player_defaults.base_max_health + trait/role/ancestry bonuses; recomputed by apply_character/apply_ancestry_effects, not persisted directly (derived at world entry) |
inventory_counts | dictionary | inventory.gd / character-carried | stackable resource counts; travels between worlds |
selected_hotbar_slot | int | player.gd / character-carried | slots 1-5 |
tool_tier | int | player.gd / character-carried | pick tier alias; tier 2 unlocks ore and speed |
effective_mine_speed | func | player.gd | tool tier, trait, and FQ-06 perk multiplier |
mine_damage_stage() | func | player.gd | FQ-08: 0-3 from mining progress; drives the transient crack overlay drawn on the target cell; never saved; resets via _reset_mining (target change, release, and on load via apply_state). Cracks rasterize through world.block_opaque_mask so they never draw outside the sprite |
block_opaque_mask(block_id) | func | world.gd | cached BitMap of a tile texture's opaque pixels (art or generated fallback; null for air/unknown); consumed by the crack overlay so degradation stays inside the visible sprite (thin trunks, leaves, bushes, torches) |
health_bar_ratio() | func | simple_threat.gd | FQ-08: hp/max_hp fill for the mini hurt bar drawn above damaged enemies on both art and fallback paths |
reach_bonus | float | character effects | extends mining/placement reach |
bush_bonus_food | int | character effects | extra food from berry bushes |
growth_threshold_delta | float | character effects | modifies settler growth threshold |
town_hall_stockpile | dictionary | town_hall.gd / save | resources deposited at hall |
town_hall.damage | float | town_hall.gd / save | 0-100 damage |
population_count | int | town_hall.gd / save | dynamic 1-8 |
day_count | int | game_root.gd / save | HUD day |
time_of_day | float | game_root.gd / save | 0-1 over 100 seconds |
is_night | bool | derived | night begins at 0.65 |
storm_active | bool | game_root.gd / save | current storm state |
storm_time_left | float | game_root.gd / save | countdown |
_storm_rolled_today | bool | game_root.gd / save | prevents repeated daily storm rolls |
threats | array | save state | saved/restored active threats: x, y, hp, max_hp, enemy_id; restored via _spawn_enemy_at |
meta.summary | dictionary | world file | day, population, coherence, damage for shell list |
v0.5 Enemy Variables
| Variable | Type | Authority | Notes |
|---|---|---|---|
enemy_id / family | string | data/enemies.json | live: surface_slime (surface), cave_crawler (underground), raider_basic (raider) |
drops[].item_id / .chance | array | data/enemies.json | rolled on death into player inventory |
spawn_rule (raider) | dictionary | data/enemies.json | day_threshold 5, stockpile_threshold 25, base_chance 0.3 |
difficulty_scaling.density_mult | float | data/enemies.json | multiplies night spawn count and raider chance |
difficulty_scaling.loot_mult | float | data/enemies.json | multiplies drop chances |
loot_mult / drop_chance_override | float | simple_threat.gd | per-instance; override is a test hook |
hall_dps | float | game_root._spawn_enemy_at | raider Town Hall damage; survives save/load |
CAVE_SPAWN_INTERVAL / cap | const | game_root.gd | 30s check, 2 active underground-family max |
contact_damage | float | data/enemies.json (live enemies) -> simple_threat.contact_damage | player damage on contact; scaled by difficulty("enemy") at spawn (mirrors hall_dps); falls back to PLAYER_DAMAGE const if the def omits it |
speed | float | data/enemies.json (live enemies) -> simple_threat.move_speed | horizontal chase speed; falls back to SPEED const if the def omits it |
hp | int | data/enemies.json (live enemies) | data-completeness/validator field; runtime hp is still set from game_root.threat_hp() (difficulty + day scaling), not read from this field |
FQ-06 Perk Variables
| Variable | Type | Authority | Notes |
|---|---|---|---|
| perk node schema | data | data/progression/perks.json | 7 lanes x 3 nodes; each node: id, display_name (title), description, effect_key, effect_value, cost, position [x,y], prerequisites (same-lane ids), xp_type_gate; validator-enforced |
purchased_perks | Array[String] | game_root.gd / world save (progression.purchased_perks) | world-owned like XP/levels; unknown ids dropped on load (silent refund) |
| perk points | derived | game_root.perk_points_total/spent/available | one point per player level above 1; spent = sum of purchased costs |
perk_state(id) | func | game_root.gd | "purchased" / "available" (prereqs owned) / "locked"; affordability gates only purchase |
player.perk_mine_speed_mult | float | game_root._apply_purchased_perk_effects -> player.apply_perk_effects | live effect_key mining_speed (Miner lane); multiplies effective_mine_speed |
player.perk_attunement_bonus | float | same | live effect_key attunement_bonus; the FQ-05 join point inside max_attunement() (no node carries it yet) |
| planning effect keys | data-only | data/progression/perks.json | detect_ore_range, cave_safety, and all non-miner lane keys stay inert until their systems ship |
| skill tree panel | UI | scripts/ui/skill_tree_panel.gd (K, toggle_skills) | scrollable node canvas from data positions; locked/available/purchased colors + [OWNED]/[LOCKED] markers; inspector + learn button; joins the Esc close chain ahead of inventory. FQ-09S: night-sky backdrop, deterministic starfield, constellation link lines per prerequisite pair (link_count() hook), star glyphs, state-colored plaques — presentation only, inspector format and purchase path unchanged |
v0.5 Progression Variables
| Variable | Type | Authority | Notes |
|---|---|---|---|
xp_totals | dictionary (float) | game_root.gd / save | per-type fractional accrual; int() at read points |
player_level | int | game_root.gd / save | 100 * 1.35^(n-1) curve from player_xp.json |
base_level | int | game_root.gd / save | ratchet 1-3 (MVP cap); one tier per check; fail-closed requires |
base_xp | int | game_root.gd / save | informational only |
_depth_hwm | int | game_root.gd / save | 10-tile depth bands for exploration XP |
effective_population_cap() | func | game_root.gd | base level's unlocks.population_cap clamped to POPULATION_MAX (camp 4, hamlet 6, village 8) |
xp_events | data | data/progression/player_xp.json | 9 events wired: enemy_defeated, block_mined, block_placed, resource_deposited, storm_survived, night_survived, subject_fed, tool_crafted, new_depth_reached |
v0.5 Ancestry Variables
| Variable | Type | Authority | Notes |
|---|---|---|---|
ancestry_move_mult / ancestry_jump_mult | float | data/ancestries.json -> player.gd | dwarf 0.9/0.85; elf jump 1.15 (from jump_bonus) |
stone_ore_mine_mult | float | same | dwarf 1.2 on stone/ore blocks |
ancestry_health_bonus | int | same | orc +25 |
health_reduction | float | same | goblin 0.8x max health (multiplicative, after bonus) |
learning_speed_mult | float | same | human 1.05x on all award_xp amounts |
phase_b_ids() | func | ancestry_registry.gd | derived from implementation_phase == "B" in data |
C/L/R Inputs
| Input | Authority | Notes |
|---|---|---|
shelter_score | settlement_model.gather_inputs | nearby solid blocks, cap 30 |
light_score | settlement_model.gather_inputs | nearby light blocks, cap 30 |
stockpile_score | settlement_model.gather_inputs | total stock adjusted by economy difficulty |
defense_score | settlement_model.gather_inputs | nearby defense-tagged blocks |
damage_score | settlement_model.gather_inputs | Town Hall damage pressure |
threat_score | game_root.current_threat_severity | night/storm/live threat severity |
scarcity_penalty | settlement_model.gather_inputs | total stock and food shortage pressure |
population_pressure | settlement_model.gather_inputs | population versus stockpile, scaled by ruler difficulty |
coherence, load_value, and resilience are formula outputs from data/settlement_rules.json, clamped to 0-100.
Validation Hooks
FQ-11 adds 7 checks (fq11_*, suite total 269) covering: station gating (a station recipe is locked until its station is built, and a station cannot be built before its prerequisite); the workbench -> furnace build chain spending build costs from the stockpile; the furnace smelting raw ore + coal into an ingot placed in the stockpile (not the player's inventory); the anvil forging the iron sword from ingots (equipped, attack 5); the metal gate (with only raw ore and no ingots, the anvil cannot produce the sword); the bronze alloy (copper + tin ingots -> bronze at the furnace); and stations_built round-tripping through save/load. validate_repo.py additionally enforces the workbench->furnace->anvil prereq chain, that furnace smelt recipes consume ore and output ingots to the stockpile, that no anvil recipe consumes raw ore (the metal gate) and each equips a real gear item into the right slot, and that the iron gear exists and out-damages the crude tier.
FQ-10 adds 5 checks (fq10_*, suite total 262) covering: the six ore families all generating at meaningful counts in a large rich world (coal/ copper/tin shallow, iron mid, silver+crystal deep); the generic starter ore vein surviving alongside them; deterministic ore-family layout across two same-seed setups (never saved); the tier gate (iron behind the tier-2 pick, coal minable at tier 1); and ore_abundance 0 clearing every ore — families and generic vein alike. validate_repo.py additionally requires the six ore blocks (self-drop, pick-preferred, tier 1..2, deeper ores held at tier 2) and the ore_table contract (each entry a real block, valid depth band, threshold in (0,1), positive frequency, unique seed_offset).
FQ-09U3 adds 8 checks (fq09u3_*, suite total 257) covering: the five stinger OGGs loading as non-looping one-shots under 8 s with the director reporting all five kinds; a fired stinger ducking the Music bus below -3 dB while the context loop keeps playing (the stinger itself rides the SFX bus, so the duck lowers the bed UNDER it, never the stinger); the duck envelope releasing back toward 0 dB after the stinger ends; per-kind cooldowns blocking a repeat while allowing a different kind and then the same kind after the cooldown; the game-event surface reaching the director — music_event("nightfall") and the player's attunement_pulsed each fire their stinger (the deferred-wiring fix: the director connects these in a call_deferred _wire_events so game_root's @onready player is live by then); music/SFX volume settings applying to the runtime buses via AudioSettings from profile-level keys (music_volume/sfx_volume) and round-tripping; and the world save carrying zero music/volume/stinger keys. AudioSettings.apply(profile[, duck_db]) is the single bus-volume authority (Music and SFX buses created at runtime); stinger timing/duck numbers live in the manifest's stinger_config (duck_db, duck_attack_db_per_sec, duck_release_db_per_sec, cooldown_seconds). The director's process_mode = ALWAYS keeps the score breathing and the duck/cooldown envelope ticking through pause. This run also landed the concurrent Codex art integration (55 generated PNGs: blocks, items, enemies with variants, ten player bodies, Town Hall, backgrounds, back walls) plus its runtime wiring (data/player_visuals.json, scripts/player/player_visual.gd, Town Hall/backdrop nearest-filtering and sizing); those surfaces carry their own validator art-contract checks.
FQ-09U2 adds 8 checks (fq09u2_*, suite total 234) covering: the mandated nesting spike executed live and its finding recorded (Synchronized-inside-Interactive PLAYS in 4.6.1; the parallel LayerPlayer design ships regardless since the suite has one shared stem set); the stem bed live — six loops at the exact grid length on the Music bus; targets following settlement coherence/resilience; the pressure stem and the collapse-edge-only fracture stem; the storm texture floor; smoothing verified to the decimal (-40 -> -37.00 dB at 6 dB/s over 0.5 s, never snapping); a deliberately length-mismatched stem set disabling layering while the context stream plays on; and save round-trips carrying zero stem/music keys with the bed surviving load. validate_repo.py additionally requires the six stem OGGs and the manifest's stem_mix contract (all six layers, min<=max dB, positive smoothing rate).
FQ-09U1 adds 9 checks (fq09u1_*, suite total 226) covering: the manifest loads with all four context loops decoded and the 72/4/64 musical grid stamped onto every stream; the director is live on the runtime-created Music bus with a four-clip AudioStreamInteractive; night/dawn/underground resolution from clean baselines; crisis enter hysteresis (a brief spike never enters, sustained 2 s does); crisis exit requiring both the lower threshold and the 6 s delay; identical states never re-request the current or pending clip; the LIVE spike proof that a next-bar same-position crossfade actually reaches the requested clip during real playback (get_current_clip_index); missing assets disabling audio silently while the state machine keeps evaluating; and save round-trips carrying zero music keys with the director surviving load. All state-machine checks call evaluate(state, delta) directly with synthetic snapshots — no wall-clock dependence. validate_repo.py additionally requires the director script/ scene, the manifest loader, the Codex render/verify tooling, the m8patch, and the four context OGGs.
FQ-09M adds 7 checks (fq09m_*, suite total 217) covering: the swing phase tracks mining state (idle -1, active >= 0, back to -1 on reset); placing a block spawns exactly one placement pulse; the attunement cast spawns its ring at the fire moment; a landed player hit sparks and a collapse adds fall-and-respawn dust; enemy hits spark (hp/drops behavior stays pinned by the fq08 checks); a successful hand craft fires the shared confirmation burst (the four forge handlers use the same _craft_confirm_fx path); and every effect self-frees within its lifetime, so the "action_fx" group returns to empty — transient by construction, nothing can reach saves. validate_repo.py additionally requires scripts/fx/action_fx.gd.
Separately in the same run, the Codex lane's scripts/audio/verify_music_assets.py mechanically verified the FQ-09U asset suite (loop sample counts, stinger lengths, stem-combination headroom); it is Codex-lane tooling, not part of the Godot smoke.
FQ-09W adds 7 checks (fq09w_*, suite total 210) covering: the natural wall map is identical across two same-seed setups with the dirt band above stone, nothing at/above the surface row, and a wall tileset carrying zero physics and zero occlusion layers (structurally unable to affect collision, lighting, shelter, or settlement math); a mined below-surface block stays a normal air delta while wall_at still reports the wall behind it; at midday the underground ambient target reaches the cave tint while the surface stays exactly DAY_TINT (asserted on ambient_target_color, not the smoothing lerp); a freshly mined open shaft admits daylight to its floor while a sealed column at the same depth stays dark (live column skylight); the backdrop node sits behind the walls layer which sits behind the Blocks layer, with null image hooks falling back to code-drawn planes; a dropped-in back_walls PNG resolves through the registry and removal falls back (self-cleaning temp art); and the saved world restores cleanly afterwards. validate_repo.py additionally requires world_backdrop.gd, the backgrounds/back_walls categories, and their art directories. The screenshot tour adds 09_underground_midday_torch.
FQ-09C adds 13 checks (fq09c_*, suite total 203) covering: the smoke run itself proves the COHERONIA_SMOKE bypass (no prologue node in the tree); exact scene order and overlay copy walked live scene by scene; the title card's three exact engine lines including By Paul Peck; completion emits finished(true) exactly once with no double-advance past the end; skip finishes safely mid-sequence with the tick clock stopped and audio silent; the profile-level seen flag round-trips through a shell reload, is idempotent, and the operator's prior value is restored; a full replay touches no characters or worlds; scene durations/cues are data-driven (42.0s, every scene declares nontrivial animation cues); the authored surface is the locked 640x360 grid at 10 Hz; every scene's draw-command fingerprint changes across ticks (a fade-only scene fails); the same scene+tick always replots identically (determinism); the cel-shot hook (a temp frame pool plays in place of the plotted shot and removal falls back); and the title screen renders the exact title/authorship/tagline labels plus the Prologue replay button alongside intact Play/Continue/Quit wiring. validate_repo.py additionally requires prologue.gd/prologue_canvas.gd/prologue_puppets.gd, all eight opening_NN_ ids in both the storyboard and the runtime, and the exact title lines in the runtime.
FQ-09 adds 4 checks (fq09_*, suite total 183) covering: toolbelt slot tiles show live counts and the gold selected-slot highlight follows the selected slot; the inventory panel's icon grid mirrors injected counts while the panel opens normally; the town stockpile grid mirrors the hall stockpile; and the acceptance flow — grids track counts through mine (+1 dirt), craft (craft_torch: 3 torches appear), deposit (stockpile grid rises; torches are not depositable), and load (grid matches the restored inventory exactly). The FQ-07 hotbar item check was rewritten to art-vs-fallback semantics (hotbar_icon_is_art) because slots now always display an icon.
FQ-08 adds 6 checks (fq08_*, suite total 179) covering: a stone target's damage stage rises from 0 to 1-3 mid-mine; the stage resets on target change and on mining stop; partial damage is transient — it survives neither save nor load, the block stays intact, and finishing the mine still yields exactly one drop; and enemy damage is visible before death (hurt-bar ratio 1.00 -> 0.67 after a non-lethal hit, enemy alive, no drops rolled).
FQ-07 adds 4 checks (fq07_*, suite total 173) covering: visual_assets.json loads with the four categories; missing images return null lookups while generated block textures still render and hotbar icons stay hidden (no crash); a runtime-written block PNG wins over the generated texture and the fallback returns after removal (pixel-verified both ways); and an item PNG lights its hotbar icon then hides on removal. The smoke writes and deletes its own temp art (with leftover cleanup at section start) so the repo stays art-free. validate_repo.py requires visual_assets.json + the art/generated dirs, fails on broken explicit references, and reports convention gaps as INFO.
FQ-06 adds 6 checks (fq06_*, suite total 169) covering: perk data loads with 7 lanes and correct miner prerequisites; at level 1 with nothing purchased the root node is available, its child locked, points 0, and purchase refused; at level 3 (2 points) purchasing stone_recovery succeeds, leaves 1 point, and multiplies effective_mine_speed by exactly 1.15; prerequisites unlock children while the 2-cost node stays unaffordable and re-purchase is refused; purchased perks + level persist through the world save round-trip with the effect re-applied; and the panel opens, selects/inspects nodes (PURCHASED/AVAILABLE states, prerequisite names, description), and closes. The toggle_skills binding joined input_actions_bound; validate_repo.py enforces the perk node schema (fields, unique ids, cost >= 1, [x,y] positions, same-lane prereqs).
FQ-05 adds 6 checks (fq05_*, suite total 163) covering: data-driven defaults (max 50); the pulse spends exactly its cost, lights up, and respects its cooldown; insufficient attunement blocks the pulse without spending; attunement regenerates over time; ancestry (attunement_bonus 20 -> max 70, attunement_regen_mult 2.0) and gear (amulet_focus -> max 80) hooks raise the maximum and removal clamps back to 50; and the current value rides the world save (21.0 round-trips). The attune_pulse binding joined the input_actions_bound list; validate_repo.py requires the five attunement player_defaults keys and the amulet_focus item.
FQ-04 adds 8 checks (fq04_*, suite total 157) covering: bare-handed baseline (attack 1, armor 0); forging the sword equips it, consumes 2 wood + 3 stone, and cannot repeat; one real hit-path strike kills a 3 hp slime; forging the armor set equips helmet/torso/feet (armor total 4) and cannot repeat; a 10-damage hit loses exactly 10 - armor_total(); a 2-damage hit under 4 armor still chips exactly 1 (no immunity); combat gear round-trips through character save/load with ancestry/trait max_health untouched; and the equipment UI shows "Attack 3 · Armor 4" plus the equipped weapon/torso names. validate_repo.py additionally requires the four combat items with meaningful effects and the two forge recipes.
FQ-03 adds 7 checks (fq03_*, suite total 149) covering: equipment.json loads with the 12 expected slots in order; a new character record carries default gear (pick_basic + 11 empty slots); equipped tool slots mirror the live tool tiers both ways; slot/item mismatches are rejected and equipping never touches the backpack; an equipped inert item (ring_band) round-trips through character save/load while empty slots stay valid; the inventory panel shows every gear slot with (empty) placeholders; and a pre-FQ-03 character (no equipment key) migrates with tool tiers and inventory preserved and gear derived from tiers. validate_repo.py additionally enforces the equipment.json schema (slot list, required items, slot_type coherence, tool item tiers).
FQ-09V adds 5 checks (fq09v_*, suite total 190) with self-cleaning smoke_tmp_* temp art: pools resolve through both the <id>_01/_02 file convention and an explicit array entry while a pool-less block reports none; variant selection is deterministic (two setups of seed 777 render identical dirt variants over a 40-cell sample with at least 2 variants in use); a different seed (778) changes the selection somewhere in the sample (nothing is stored); removing the pool falls back to exactly one source with the generated texture; and the live world state is restored afterwards.
FQ-09S adds 1 check (fq09s_constellation_links_match_prereqs, bringing the suite to 185 at that point): the star-map canvas draws exactly one constellation link per prerequisite pair in the live lane, with the expected count derived from the same perk_lanes() data the buttons use — presentation cannot invent or drop an edge. All six fq06_* checks (states, purchase, prereq/cost gates, persistence, inspection text) pass unchanged against the restyled panel.
A post-FQ-09R hardening run adds 1 check (fq08_crack_mask_inside_sprite, suite total 184): world.block_opaque_mask is fully opaque for stone, opaque at the trunk bar but transparent at both tile edges for tree_trunk, and null for air — pinning that the crack overlay's per-pixel mask keeps degradation inside the visible sprite.
FQ-09R replaces the 8 FQ-02 checks with 8 fq09r_* checks (suite total unchanged at 183) covering: tree_density 0 clears trunks and leaves; default generation produces trees with leaves (trunk and leaf counts both > 0); every generated tree cell is non-solid and bare-hand mineable (one tree class — no walk-past-only kind); density 2.0 grows more trunks than default on the same seed; mining a trunk yields exactly 1 wood through the normal drop path and leaves an air delta; clearing a leaf cell changes no inventory count; and the live traversal pair — a surface trunk is found on flat terrain and the player walks past it using only move_right (no jump, no mining). The baseline mining checks (mineable_blocks_found, hardness_orders_mining_time, wave_f_*) now harvest tree_trunk, preserving the dirt < trunk < stone ordering and the axe speed-up on the same wood-hardness value.
FQ-01 adds 8 checks (fq01_*) covering: i-frame damage gating and the post-cooldown hit; eat-food healing (consumes 1 food, clamps to max_health) and its full-health no-op; passive regen near vs. far from the Town Hall; collapse inventory loss (floored per stack) plus respawn-at-hall-at-full-health; health save/load round-trip with max_health preserved; and enemy contact damage read from data/enemies.json scaled by difficulty("enemy").
The v0.6 smoke suite (122 checks) additionally verifies: ancestry detail text (dwarf effects + constraint, planned label for non-live); world_settings ui_help coverage of all six axes and preset deviations; character inventory isolation (two characters, one world; second world; no starter-item duplication; legacy migration); toggle_inventory binding and panel open/close/content; berry-bush support cleanup on mine/load/regrowth; axe crafting, axe-vs-pick speed differentiation (33 -> 24 frames at wood hardness — originally on solid wood columns, harvested from tree_trunk since FQ-09R; stone unchanged), and {pick, axe} save round-trips with legacy tool-tier migration.
The v0.5 smoke suite (90 checks) additionally verifies: enemies.json loads with 3 live defs; each live enemy spawns with its def id; drops enter the inventory; enemy save/load round-trips id/hp/max_hp/hall_dps; progression JSONs load; XP awards accrue (including human fractional bonus 21 vs 20 over 20 events); level curve; base level advances camp -> hamlet; population caps at 4/6/8 by level and growth is gated; underground threats survive dawn while surface threats are freed; peaceful rule blocks cave spawns; ancestry effects (dwarf mults, orc health, goblin reduction, elf jump) and unknown-species baseline.
The v0.4 smoke suite verifies:
- shell character/world persistence
- presets and world config overlay
- role items and character trait effects
- input bindings
- movement, jump, mining, drops, hardness order
- ore/tool-tier gate and tier-2 speed
- berry food and regrowth
- farming: till, plant, grow, harvest, no-float/no-regrow, timer save/load, food-yard score
- placement, torches, occlusion, lanterns
- deposit, population food use, population floor/cap/growth
- rule toggles for food, weather, darkness
- enemy difficulty and impressionability scaling
- enemy variety (FQ-13): thornrat crop-eating, ore tick near ore, torchbearer hall burn, per-def hp/dps profiles, drops
- goal panel (FQ-14): state-derived objectives, prefix-latch (no regress), toggle, no save state
- map/scouting (FQ-15): reveal-on-explore bands, hall/player/ore/threat markers, discovered-band persistence, biome_reveal scouting hook
- C/L/R reaction to light and threats
- storm pressure, damage, and roof mitigation
- save/load of player, terrain, stockpile, lights, threats, tool tier, regrow timer, crop-growth timers, storm state, discovered map bands
- world size, ore abundance, density controls, and per-block seed variation
Required Audit Behavior
After implementation runs, update this file when variables are added, removed, renamed, or moved between authority surfaces. A run is not SIGNABLE if C/L/R values are decorative or disconnected from world state.