Phase 0 scaffold + asset audit findings
Project scaffold:
- project.godot at repo root, GL Compatibility renderer (max mobile reach),
pixel-snap on, texture filter nearest, sensor_landscape orientation
- 7 autoloads: World, Sim, GameState, EventBus, Strings, Audit, SaveSystem
- scenes/main/main.{tscn,gd} smoke-test scene with autoload assertions
- Folder layout matches tavernkeep idiom: autoload/ at root, scripts
co-located with scenes/ (not the scripts/autoloads/ mirror originally
sketched in implementation.md)
- Input map: pause, speed_cycle, speed_normal/fast/ultra, confirm, cancel.
Mobile gestures (pinch/drag/long-press) handled at script level via
Godot's InputEventScreenTouch/Drag/MagnifyGesture.
- SaveSystem skeleton: SAVE_VERSION=1, JSON to user://save_slot.json,
version-mismatch warning. Phase 3 expands to real entity state.
- icon.svg placeholder (cabin silhouette on dark green field)
- README.md points at memory.md / implementation.md / docs/
Headless verification: 'godot --headless --path . --quit' exits 0,
'[main] Phase 0 smoke test online.' prints, no errors. Editor-side
green-dot check still pending — needs human launch of editor.
Asset audit (researcher Haiku, 2026-05-10):
- FG_Houses.png NOT autotile-solvable — pre-built decorative house
compositions, 4 distinct roof palettes, no modular wall family.
~½–1 day per material to author terrain bits on top.
- FG_Fortress.png IS autotile-solvable — ~20–30 modular tan-stone
pieces. Wang-style Godot 4 terrain works with minimal extra art.
Iconic Homestead $19.99 fallback not needed.
- No wolf sprite anywhere in the bundle. EvoMonster packs all
cute/fantasy. Need commission, CC0 source, or Ventilatore check.
- Retro Graveyard 16x16 [Kingdom Explorer] confirmed in Tier 3 with
full graveyard suite — direct use in Phase 14.
New open questions surfaced in memory.md:
- Player-built wall material strategy (3 options laid out)
- Wolf sprite acquisition path (Phase 10 blocker)
Project move:
- Repo physical location moved from ~/claude/projects/rimlike to
/mnt/d/godot/rimlike (D: drive, fast for Windows-side editor).
- Symlink at the original WSL path preserves the home-CLAUDE.md
layout convention. Mirrors tavernkeep's pattern.
- Set core.filemode=false to silence DrvFs's everything-is-0777
false-positive on git diff.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
daf26ed27a
commit
128294c14f
33 changed files with 389 additions and 35 deletions
12
autoload/audit.gd
Normal file
12
autoload/audit.gd
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
extends Node
|
||||
## Debug-only logging + perf counters. No-op in release builds.
|
||||
##
|
||||
## Use Audit.log("category", "message") instead of print() — gives us a single
|
||||
## switch to silence everything before ship.
|
||||
|
||||
var enabled: bool = OS.is_debug_build()
|
||||
|
||||
|
||||
func log(category: String, message: String) -> void:
|
||||
if enabled:
|
||||
print("[%s] %s" % [category, message])
|
||||
9
autoload/event_bus.gd
Normal file
9
autoload/event_bus.gd
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
extends Node
|
||||
## Pure signal hub — notification-only, no state, no callbacks back into singletons.
|
||||
##
|
||||
## Subsystems mutate themselves; this bus only spreads the news. Add signals as
|
||||
## features land — keep this file readable. See docs/architecture.md.
|
||||
|
||||
# Phase 0 placeholder — no signals yet.
|
||||
# Phase 1 will add tick / speed-change / pause signals.
|
||||
# Phase 2 will add pawn-state signals (selected, deselected, walking, …).
|
||||
28
autoload/game_state.gd
Normal file
28
autoload/game_state.gd
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
extends Node
|
||||
## Top-level mutable game state. SaveSystem serializes via save_dict() / apply_dict().
|
||||
##
|
||||
## Holds session-wide flags and the "what map are we on" pointer. Per-entity state
|
||||
## lives on the entities themselves; per-tile state on the TileMap or World.
|
||||
## See docs/architecture.md.
|
||||
|
||||
var current_map_id: StringName = &"slice_temperate_forest"
|
||||
var session_started_at_unix: int = 0 # for "you've been away X minutes" toast
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
session_started_at_unix = int(Time.get_unix_time_from_system())
|
||||
|
||||
|
||||
# Phase 16 expands these into real save round-trip.
|
||||
func save_dict() -> Dictionary:
|
||||
return {
|
||||
"current_map_id": String(current_map_id),
|
||||
"session_started_at_unix": session_started_at_unix,
|
||||
}
|
||||
|
||||
|
||||
func apply_dict(d: Dictionary) -> void:
|
||||
if d.has("current_map_id"):
|
||||
current_map_id = StringName(d["current_map_id"])
|
||||
if d.has("session_started_at_unix"):
|
||||
session_started_at_unix = int(d["session_started_at_unix"])
|
||||
45
autoload/save_system.gd
Normal file
45
autoload/save_system.gd
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
extends Node
|
||||
## Save/load skeleton — version field, file IO, save-between-ticks contract.
|
||||
##
|
||||
## Saves only happen between sim ticks (Sim owns the loop). JobRunner mid-toil
|
||||
## state must round-trip from day one — see docs/architecture.md.
|
||||
##
|
||||
## Phase 0: file-IO smoke test only. Phase 3 expands to real entity state.
|
||||
## Phase 16 closes coverage of every system.
|
||||
|
||||
const SAVE_VERSION: int = 1
|
||||
const SAVE_PATH: String = "user://save_slot.json"
|
||||
|
||||
|
||||
func write_save() -> bool:
|
||||
# Smoke-test payload. Phase 3 expands this.
|
||||
var payload := {
|
||||
"version": SAVE_VERSION,
|
||||
"sim_tick": Sim.tick,
|
||||
"game_state": GameState.save_dict(),
|
||||
}
|
||||
var f := FileAccess.open(SAVE_PATH, FileAccess.WRITE)
|
||||
if f == null:
|
||||
push_error("SaveSystem.write_save: cannot open %s" % SAVE_PATH)
|
||||
return false
|
||||
f.store_string(JSON.stringify(payload))
|
||||
return true
|
||||
|
||||
|
||||
func read_save() -> Dictionary:
|
||||
if not FileAccess.file_exists(SAVE_PATH):
|
||||
return {}
|
||||
var f := FileAccess.open(SAVE_PATH, FileAccess.READ)
|
||||
if f == null:
|
||||
return {}
|
||||
var raw := f.get_as_text()
|
||||
var parsed: Variant = JSON.parse_string(raw)
|
||||
if typeof(parsed) != TYPE_DICTIONARY:
|
||||
push_error("SaveSystem.read_save: corrupt save")
|
||||
return {}
|
||||
if int(parsed.get("version", 0)) != SAVE_VERSION:
|
||||
push_warning(
|
||||
"SaveSystem.read_save: version mismatch (%s vs %s)" %
|
||||
[parsed.get("version", "?"), SAVE_VERSION]
|
||||
)
|
||||
return parsed
|
||||
28
autoload/sim.gd
Normal file
28
autoload/sim.gd
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
extends Node
|
||||
## Sim tick loop owner. Sim runs at 20 Hz, render at 60 Hz, decoupled.
|
||||
##
|
||||
## Speed factor controls how many sim ticks queue per render frame:
|
||||
## 1× — 1 tick per 3 render frames (20 ticks/s)
|
||||
## Fast — 5 ticks per 3 render frames (~100 ticks/s)
|
||||
## Ultra — 12 ticks per 3 render frames (~240 ticks/s)
|
||||
## Pause — 0
|
||||
##
|
||||
## Saves are taken between ticks only — never mid-tick — so JobRunner mid-toil
|
||||
## state round-trips cleanly. See docs/architecture.md "Time / tick model".
|
||||
|
||||
const SIM_HZ: int = 20
|
||||
const TICK_INTERVAL_S: float = 1.0 / float(SIM_HZ)
|
||||
|
||||
enum Speed { PAUSE, NORMAL, FAST, ULTRA }
|
||||
|
||||
const SPEED_FACTOR: Dictionary = {
|
||||
Speed.PAUSE: 0,
|
||||
Speed.NORMAL: 1,
|
||||
Speed.FAST: 5,
|
||||
Speed.ULTRA: 12,
|
||||
}
|
||||
|
||||
var current_speed: Speed = Speed.PAUSE
|
||||
var tick: int = 0
|
||||
|
||||
# Tick-loop body lands in Phase 1.
|
||||
22
autoload/strings.gd
Normal file
22
autoload/strings.gd
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
extends Node
|
||||
## i18n string table — player-visible strings ONLY. Code keys, EN values.
|
||||
##
|
||||
## Locked from day one (per CLAUDE.md): no hardcoded display copy in scenes or
|
||||
## scripts. If you have player-facing text, add a key here and call Strings.t(key).
|
||||
##
|
||||
## Locale switching is post-MVP; the indirection lands now so we don't have to
|
||||
## retrofit the whole game later. When the table grows, move it to a .tres or
|
||||
## external CSV import; the public API (`Strings.t(key)`) stays the same.
|
||||
|
||||
const TABLE: Dictionary = {
|
||||
# Phase 0 placeholder — populate as features land.
|
||||
&"app.title": "Rimlike",
|
||||
&"smoke.hello": "Phase 0 — autoloads online.",
|
||||
}
|
||||
|
||||
|
||||
func t(key: StringName) -> String:
|
||||
if TABLE.has(key):
|
||||
return TABLE[key]
|
||||
push_warning("Strings.t(): missing key %s" % key)
|
||||
return String(key)
|
||||
14
autoload/world.gd
Normal file
14
autoload/world.gd
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
extends Node
|
||||
## Runtime entity registry + tile-related sim state.
|
||||
##
|
||||
## All gameplay entities (pawns, items, furniture, animals, corpses) live here.
|
||||
## TileMap data is owned by the world-view scene; World holds the *indirect*
|
||||
## state (designation queue, dirty-haul set, zone records, etc.) that doesn't
|
||||
## belong on the TileMap itself.
|
||||
##
|
||||
## See docs/architecture.md.
|
||||
|
||||
# Phase 1 will add the entity registries (pawns, items, furniture).
|
||||
# Phase 4 will add `items_needing_haul` (the dirty set).
|
||||
# Phase 5 will add the BuildJob queue.
|
||||
# Phase 13 will add zones / rooms / dirtiness.
|
||||
Loading…
Add table
Add a link
Reference in a new issue