chore(godot): 🔧 Update Godot project config, pnpm setup, linting/formatting rules, and asset documentation
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
6
.gutconfig.json
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"dirs": ["res://engine/tests/unit"],
|
||||
"include_subdirs": true,
|
||||
"should_exit": true,
|
||||
"log": 1
|
||||
}
|
||||
45
.pnpmfile.cjs
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
/**
|
||||
* Rewrite workspace:* dependencies to their published Verdaccio versions.
|
||||
*
|
||||
* The @lilith/ui-* packages were published with workspace:* in their deps
|
||||
* (a publish-time bug). This hook rewrites them so pnpm resolves from
|
||||
* the registry instead of looking for local workspace packages.
|
||||
*
|
||||
* Unknown workspace:* deps (e.g. @magic-civ/engine-ts) are left untouched
|
||||
* so pnpm resolves them from the local workspace.
|
||||
*/
|
||||
const WORKSPACE_VERSIONS = {
|
||||
'@lilith/ui-styled-components': '^6.3.9',
|
||||
'@lilith/ui-design-tokens': '^1.2.1',
|
||||
'@lilith/ui-utils': '^2.0.0',
|
||||
'@lilith/ui-zname': '^1.2.4',
|
||||
'@lilith/ui-glassmorphism': '^1.1.6',
|
||||
'@lilith/ui-motion': '^2.2.0',
|
||||
'@lilith/ui-primitives': '^1.2.16',
|
||||
'@lilith/ui-theme': '^1.5.0',
|
||||
'@lilith/ui-feedback': '^1.4.0',
|
||||
'@lilith/ui-animated': '^1.1.7',
|
||||
'@lilith/ui-typography': '^1.1.7',
|
||||
'@lilith/ui-layout': '^1.2.0',
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
hooks: {
|
||||
readPackage(pkg) {
|
||||
for (const depType of ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies']) {
|
||||
const deps = pkg[depType]
|
||||
if (!deps) continue
|
||||
for (const [name, version] of Object.entries(deps)) {
|
||||
if (version.startsWith('workspace:')) {
|
||||
const resolved = WORKSPACE_VERSIONS[name]
|
||||
if (resolved) {
|
||||
deps[name] = resolved
|
||||
}
|
||||
// Unknown workspace:* deps are left as-is for pnpm to resolve locally
|
||||
}
|
||||
}
|
||||
}
|
||||
return pkg
|
||||
},
|
||||
},
|
||||
}
|
||||
307
CLAUDE.md
Normal file
|
|
@ -0,0 +1,307 @@
|
|||
# Magic Civilization
|
||||
|
||||
Fantasy 4X turn-based strategy game (Civ5 + Master of Magic + Magic: The Gathering color pie) in Godot 4 / GDScript. 16 races, 5 magic schools, hex grid.
|
||||
|
||||
> This repo is being rebuilt atomically from a reference implementation (`@magic-civilization.messy/`). Port systems as each milestone requires them — never reference `.messy/` paths from runtime code.
|
||||
|
||||
## Scope
|
||||
|
||||
**Early access demo** ("Age of Four") — 4 races (High Elf, Human, Dwarf, Orc), all 5 magic schools, full 4X + magic loop. See `.project/ROADMAP.md` for scope and build sequence.
|
||||
|
||||
## Tech Stack
|
||||
|
||||
- **Engine:** Godot 4.x
|
||||
- **Language:** GDScript only (no C#, no GDExtension)
|
||||
- **Data:** JSON game packs (`games/age-of-four/data/*.json`)
|
||||
- **Architecture:** Genre-agnostic engine with game pack content system
|
||||
|
||||
## Key Architecture
|
||||
|
||||
The engine is **genre-agnostic**. All game content and display text comes from game packs. The fantasy game "Age of Four" is the default. See `docs/engine/ABSTRACTION.md` (to be written).
|
||||
|
||||
- UI labels resolve through `ThemeVocabulary.lookup(engine_key)` — never hardcode theme strings
|
||||
- Sprites resolve through `ThemeAssets.resolve(path)` — never hardcode asset paths
|
||||
- Systems communicate via `EventBus` signals — never directly reference other systems
|
||||
- All game content is data-driven from JSON — don't hardcode stats, costs, or effects
|
||||
- **5 eras, 5 tiers** — everything is tiered 1-5 aligned with the 5 eras (units, spells, buildings, wild creatures). Spells use `scope: "global"` (High Archon, world map) or `scope: "local"` (specialist units, combat). School tech tiers map: T1-T2 spells gated by Mysticism/Arcane Lore, T3-T5 by school techs.
|
||||
|
||||
## Documentation
|
||||
|
||||
See `README.md` for the full doc index. Docs live in two places:
|
||||
|
||||
- **`engine/docs/`** — genre-agnostic engine architecture (written as engine systems are built)
|
||||
- **`games/age-of-four/docs/`** — fantasy game design (races, combat, spells, economy, etc.)
|
||||
|
||||
Build process docs (roadmap, feature gap, task lists) stay in `.project/`.
|
||||
|
||||
## Single Source of Truth: GDScript → TypeScript
|
||||
|
||||
> **The GDScript climate engine is the source of truth for all simulation logic. The TypeScript in `guide/` is generated output — never edited directly. If the guide gives wrong results, fix the GDScript and re-run the transpiler.**
|
||||
|
||||
The climate simulation runs in two runtimes: GDScript (Godot game engine) and TypeScript (web guide). GDScript is written first; TypeScript is auto-generated from it via `lilith-gdscript-transpiler`.
|
||||
|
||||
### Architecture
|
||||
|
||||
```
|
||||
games/age-of-four/data/climate_spec.json ← Canonical thresholds, events, ley rules
|
||||
↓ read at runtime by both engines
|
||||
engine/src/modules/climate/climate.gd ← SOURCE OF TRUTH (physics steps)
|
||||
engine/src/modules/climate/ecological_events.gd ← SOURCE OF TRUTH (stochastic events)
|
||||
engine/src/modules/climate/anchor_decay.gd ← SOURCE OF TRUTH (ley anchor decay)
|
||||
engine/src/modules/climate/climate_spec_eval.gd ← SOURCE OF TRUTH (spec condition evaluator)
|
||||
↓ transpiled by tools/transpile-engine/transpile.py
|
||||
packages/engine-ts/src/ClimatePhysics.generated.ts ← AUTO-GENERATED, never edit
|
||||
packages/engine-ts/src/index.ts ← Re-exports for @magic-civ/engine-ts
|
||||
guide/age-of-four/ ← Consumes @magic-civ/engine-ts (workspace:*)
|
||||
```
|
||||
|
||||
### Transpiler Package
|
||||
|
||||
`lilith-gdscript-transpiler` — custom GDScript→TypeScript transpiler.
|
||||
|
||||
- **Package**: `@packages/@py/gdscript-transpiler` in this workspace
|
||||
- **PyPI index**: `https://forge.nasty.sh/api/packages/lilith/pypi/simple/`
|
||||
- **Invocation**: `uv run tools/transpile-engine/transpile.py` (self-contained inline script — `uv` handles deps automatically, no manual pip install needed)
|
||||
- **Check mode**: `uv run tools/transpile-engine/transpile.py --check` (exits 1 if generated file is stale vs GDScript source)
|
||||
- **Sources**: `climate.gd`, `ecological_events.gd`, `anchor_decay.gd`, `climate_spec_eval.gd`
|
||||
- **Output**: `packages/engine-ts/src/ClimatePhysics.generated.ts`
|
||||
|
||||
```bash
|
||||
uv run tools/transpile-engine/transpile.py # regenerate
|
||||
uv run tools/transpile-engine/transpile.py --check # CI: exit 1 if stale
|
||||
```
|
||||
|
||||
### Rules
|
||||
|
||||
- **Never edit `ClimatePhysics.generated.ts`** — it's auto-generated from GDScript
|
||||
- **Never hardcode terrain thresholds** — read from `climate_spec.json`
|
||||
- **All simulation changes go in GDScript first** — then re-run transpiler
|
||||
- **Ecological events require a seed** — `process_turn(game_map, turn, seed)` for deterministic PRNG
|
||||
- **Golden test vectors** verify both engines produce identical output on the same seed
|
||||
|
||||
## Project-Specific Agents
|
||||
|
||||
10 specialized agents for game development in `.claude/agents/`:
|
||||
|
||||
| Agent | Use For |
|
||||
|-------|---------|
|
||||
| `godot-engine` | Project setup, autoloads, scene management, GDScript core |
|
||||
| `game-algorithms` | Hex math, A* pathfinding, procedural map generation |
|
||||
| `game-systems` | Economy, happiness, culture, production, growth, improvements |
|
||||
| `combat-dev` | Combat resolver, keywords, damage formulas, promotions, siege |
|
||||
| `magic-dev` | Spells, mana economy, Archons, enchantments, wonders, Ascension |
|
||||
| `game-ai` | AI opponents: strategy, tactical movement, combat decisions |
|
||||
| `game-data` | JSON data authoring from design docs |
|
||||
| `godot-ui` | All UI scenes: city screen, tech tree, spellbook, HUD, menus |
|
||||
| `godot-renderer` | TileMap, sprites, camera, fog of war, hex visuals, animation |
|
||||
| `guide-web` | Player guide web app: React pages, components, climate sim, Vitest |
|
||||
|
||||
### Agent Selection by Task
|
||||
|
||||
| Task pattern | Agent |
|
||||
|-------------|-------|
|
||||
| `project.godot`, autoloads, `SceneManager`, save/load | `godot-engine` |
|
||||
| Hex coordinates, A*, map generation, tile storage | `game-algorithms` |
|
||||
| `economy.gd`, `happiness.gd`, `culture.gd`, city production, growth | `game-systems` |
|
||||
| `combat_resolver.gd`, keywords, flanking, ZOC, promotions | `combat-dev` |
|
||||
| `spell_system.gd`, `mana_pool.gd`, `archon.gd`, spells, wonders | `magic-dev` |
|
||||
| `ai_player.gd`, AI decisions, difficulty modifiers | `game-ai` |
|
||||
| `*.json` data files, `vocabulary.json`, `game.json` | `game-data` |
|
||||
| `*.tscn` UI scenes, HUD panels, overlays, menus | `godot-ui` |
|
||||
| TileMap, sprites, camera, fog, selection highlight, animation | `godot-renderer` |
|
||||
| `guide/age-of-four/`, `guide/engine/`, React, Vite, climate sim | `guide-web` |
|
||||
|
||||
## Conventions
|
||||
|
||||
### GDScript Style
|
||||
- snake_case for variables, functions, files
|
||||
- PascalCase for classes and nodes
|
||||
- Signals use past tense: `unit_moved`, `city_founded`, `tech_researched`
|
||||
- Constants in UPPER_SNAKE_CASE
|
||||
- Type hints on all function signatures
|
||||
|
||||
### Class Resolution — Preload Pattern (critical)
|
||||
|
||||
Godot 4 `class_name` registration is unreliable in autoload context. **Always reference non-autoload classes via `preload()` const**, never by bare class_name:
|
||||
|
||||
```gdscript
|
||||
const CityScript = preload("res://engine/src/entities/city.gd")
|
||||
const HexUtilsScript = preload("res://engine/src/map/hex_utils.gd")
|
||||
const UnitScript = preload("res://engine/src/entities/unit.gd")
|
||||
const TileScript = preload("res://engine/src/map/tile.gd")
|
||||
const PlayerScript = preload("res://engine/src/entities/player.gd")
|
||||
const GameMapScript = preload("res://engine/src/map/game_map.gd")
|
||||
|
||||
var unit: UnitScript = UnitScript.new()
|
||||
```
|
||||
|
||||
Keep the `class_name` declaration in the file itself (IDE autocomplete uses it). All runtime references use the preload const.
|
||||
|
||||
### Entity Class Pattern
|
||||
|
||||
All game entities (`Unit`, `City`, `Player`, `Building`, `Improvement`) are:
|
||||
- `class_name Foo / extends RefCounted` — pure data, no scene/node
|
||||
- Rendering handled by separate renderer scripts
|
||||
- Logic calls `DataLoader` for type definitions, `EventBus` for state-change signals
|
||||
|
||||
### Signal Parameters
|
||||
|
||||
`EventBus` signals pass entity objects as `Variant`, not typed class_name parameters:
|
||||
|
||||
```gdscript
|
||||
# Correct:
|
||||
signal unit_moved(unit: Variant, from: Vector2i, to: Vector2i)
|
||||
# Wrong — causes type errors in autoload context:
|
||||
signal unit_moved(unit: Unit, from: Vector2i, to: Vector2i)
|
||||
```
|
||||
|
||||
### Hex Math
|
||||
|
||||
All hex coordinate math goes through `HexUtils` static methods — never inline the formulas. `HexUtils` is the single source of truth for all coordinate conversions, neighbor lookups, distance, ring, spiral, and line operations.
|
||||
|
||||
**Direction-to-edge mapping** — AXIAL direction indices and hex polygon edge indices are NOT the same:
|
||||
|
||||
```
|
||||
Direction: 0(E) 1(NE) 2(NW) 3(W) 4(SW) 5(SE)
|
||||
Poly edge: 2 1 0 5 4 3
|
||||
```
|
||||
|
||||
### Data IDs
|
||||
- snake_case: `high_elf_heritage`, `chaos_magic`, `fire_elemental`
|
||||
- Match the `id` field in JSON data files
|
||||
- Reference by ID in code, resolve display name through `ThemeVocabulary`
|
||||
|
||||
### File Organization
|
||||
```
|
||||
engine/
|
||||
src/
|
||||
autoloads/ — singletons (GameState, TurnManager, DataLoader, EventBus, ThemeVocabulary, ThemeAssets)
|
||||
entities/ — game objects (unit, city, building, improvement, archon)
|
||||
map/ — tile storage, hex utils, pathfinder, game_map
|
||||
generation/ — map generator, terrain refiner, hydrology, wind
|
||||
rendering/ — hex, city, unit, fog, river, road, indicator renderers
|
||||
core/ — save manager, pending actions, terrain affinity
|
||||
modules/
|
||||
combat/ — combat_resolver, keyword_handler
|
||||
magic/ — spell_system, mana_pool, infusion_system
|
||||
climate/ — climate, atmosphere, weather, ecological_events
|
||||
ley/ — ley_network
|
||||
empire/ — economy, happiness, culture, government
|
||||
management/ — city_manager, unit_manager, improvement_manager
|
||||
events/ — natural_events
|
||||
victory/ — victory_manager, ascension_ritual
|
||||
ai/ — ai_player, ai_tactical, ai_city, ai_magic_research, ai_military
|
||||
tech/ — tech_web
|
||||
scenes/
|
||||
main/ — entry point
|
||||
menus/ — main_menu, game_setup, options, load_game
|
||||
world_map/ — overworld
|
||||
city/ — city management overlay
|
||||
tech_tree/ — tech web overlay
|
||||
magic/ — spellbook, mana, archon overlays
|
||||
combat/ — combat popups
|
||||
hud/ — top_bar, minimap, unit_panel
|
||||
tests/ — proof scenes (.tscn + .gd co-located)
|
||||
tests/
|
||||
unit/ — GUT tests by module
|
||||
integration/ — end-to-end integration tests
|
||||
|
||||
games/
|
||||
age-of-four/
|
||||
data/ — all JSON content (units/, spells/, techs/, terrain/, ...)
|
||||
assets/ — sprites/, icons/
|
||||
game.json — game manifest
|
||||
|
||||
guide/
|
||||
engine/ — @magic-civ/guide-engine (shared UI components, types, utils)
|
||||
age-of-four/ — @magic-civilization/guide-age-of-four (all pages, app shell)
|
||||
```
|
||||
|
||||
## DX Tooling
|
||||
|
||||
### Testing & Linting
|
||||
- **GUT** (Godot Unit Test) — unit + integration tests for GDScript
|
||||
- **gdtoolkit** (`pip install gdtoolkit`) — `gdlint` + `gdformat`
|
||||
- Run tests headless: `godot --headless --script res://addons/gut/gut_cmdln.gd`
|
||||
- Run lint: `gdlint engine/src/`
|
||||
|
||||
### Task Runner (`./run`)
|
||||
|
||||
Central entry point for dev, export, deploy commands.
|
||||
|
||||
```bash
|
||||
./run play # Launch the game locally
|
||||
./run editor # Open Godot editor
|
||||
./run lint # gdlint engine/src/
|
||||
./run test # GUT tests headless
|
||||
./run screenshot [name] [scene] # Capture + SCP to plum
|
||||
./run export [version] # All platforms in parallel
|
||||
```
|
||||
|
||||
### Screenshot & Visual Verification
|
||||
|
||||
```bash
|
||||
./tools/screenshot.sh [name] [scene] [delay]
|
||||
```
|
||||
|
||||
Screenshots are captured to Flatpak user data and SCP'd to `plum:~/Desktop/magic_civ_<name>.png`.
|
||||
|
||||
### Proof Scenes (`engine/scenes/tests/`)
|
||||
|
||||
Self-capturing test scenes. Each phase should have a proof scene that sets up minimal game state, renders claimed features, auto-captures, and quits.
|
||||
|
||||
### Environment Config (`.env.*`)
|
||||
|
||||
`EnvConfig` autoload reads `.env` (base) then `.env.development` (overrides) at startup:
|
||||
- `.env.production` — `FORCE_DISABLE_FOGOFWAR=false`
|
||||
- `.env.development` — `FORCE_DISABLE_FOGOFWAR=true`, `FORCE_UNLIMITED_RESEARCH=true`
|
||||
|
||||
### DataLoader — File vs Directory Pattern
|
||||
|
||||
`DataLoader` supports two layouts per data category:
|
||||
- **Single file:** `games/age-of-four/data/races.json`
|
||||
- **Split directory:** `games/age-of-four/data/units/` — reads all `.json` files, merges by `id`
|
||||
|
||||
**Never create a monolithic file that exceeds 500 lines.**
|
||||
|
||||
### Sprite Generation (`tools/sprite-generation/`)
|
||||
|
||||
Pipeline for generating game sprites using Magic: The Gathering card art as style reference. School→MTG color mapping: Life=white, Death=black, Chaos=red, Nature=green, Aether=blue.
|
||||
|
||||
Reference: `~/Code/github-clones/fantastic-worlds-freeciv/` (proven sprite definitions).
|
||||
|
||||
**Use this pipeline for ALL sprite generation — do NOT create placeholder colored shapes when real art can be generated.**
|
||||
|
||||
## Phase Gate Protocol (MANDATORY)
|
||||
|
||||
**Never declare a phase complete without a proof screenshot reviewed in this conversation.**
|
||||
|
||||
A phase is NOT done until:
|
||||
1. A proof scene (`engine/scenes/tests/`) renders ALL claimed features in one screenshot
|
||||
2. The screenshot is captured via `tools/screenshot.sh`
|
||||
3. The screenshot is SCP'd to `plum:~/Desktop/magic_civ_<phase>_proof.png`
|
||||
4. The screenshot is read and reviewed IN THIS CONVERSATION
|
||||
5. Every claimed feature is visibly confirmed
|
||||
6. The user approves it
|
||||
|
||||
Code exists ≠ code works. Tests pass ≠ features render. Lint clean ≠ visually correct.
|
||||
|
||||
## Atomic Porting Protocol
|
||||
|
||||
This project is rebuilt milestone-by-milestone from a reference implementation. **Only port what the current milestone requires.**
|
||||
|
||||
- **Data files**: only copy JSON files that the current milestone's code actually loads — not the full 99-file set "for completeness"
|
||||
- **Engine code**: only port systems listed in the current milestone's task list — don't pull in combat when building climate
|
||||
- **Scenes**: only create scenes needed for the current phase's proof screenshot
|
||||
- **Tests**: only port tests for systems that exist in this repo — a test for code that hasn't been ported yet is dead weight
|
||||
- If a file from the reference doesn't have a consumer in this repo yet, it doesn't belong here yet
|
||||
- The milestone task lists in `.project/tasks/` define exactly what comes in and when
|
||||
|
||||
## Safety Rules
|
||||
- Never hardcode theme-specific strings in engine code
|
||||
- Never hardcode asset paths — always use `ThemeAssets.resolve()`
|
||||
- `GameState` must support multiple map layers (future Ethereal Plane)
|
||||
- All system state changes emit signals via `EventBus`
|
||||
- Building/unit effects are data-driven from JSON — don't hardcode behavior
|
||||
- Always call `DataLoader.load_game("age-of-four")` when running scenes directly
|
||||
- **NEVER use anime models for game art** — use `juggernaut-xl-v9`, `epicrealism-xl`, `illustrious-xl-v2`
|
||||
67
README.md
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
# Magic Civilization
|
||||
|
||||
Fantasy 4X turn-based strategy game (Civ5 + Master of Magic + MTG color pie) in Godot 4 / GDScript.
|
||||
|
||||
## Structure
|
||||
|
||||
```
|
||||
engine/ — genre-agnostic game engine (GDScript)
|
||||
src/ — autoloads, entities, map, generation, modules, rendering
|
||||
scenes/ — Godot scenes (.tscn + .gd)
|
||||
docs/ — engine architecture docs
|
||||
|
||||
games/
|
||||
age-of-four/ — fantasy game pack (the default theme)
|
||||
data/ — all JSON game content
|
||||
assets/ — sprites, icons
|
||||
docs/ — game design docs (races, combat, spells, economy)
|
||||
|
||||
guide/ — player guide web app (React/TypeScript)
|
||||
engine/ — @magic-civ/guide-engine (shared components)
|
||||
age-of-four/ — guide pages for Age of Four
|
||||
|
||||
packages/
|
||||
engine-ts/ — @magic-civ/engine-ts (auto-generated climate simulation)
|
||||
|
||||
tools/ — sprite generation, transpiler, screenshot capture
|
||||
```
|
||||
|
||||
## Docs
|
||||
|
||||
### Engine (`engine/docs/`)
|
||||
|
||||
| Doc | Covers |
|
||||
|-----|--------|
|
||||
| ARCHITECTURE.md | Project structure, autoloads, scene lifecycle |
|
||||
| ABSTRACTION.md | Genre-agnostic vocabulary, theme pack system |
|
||||
| DATA_FORMAT.md | JSON data schemas, DataLoader patterns |
|
||||
|
||||
### Game Design (`games/age-of-four/docs/`)
|
||||
|
||||
| Doc | Covers |
|
||||
|-----|--------|
|
||||
| [RACES.md](games/age-of-four/docs/RACES.md) | 4 demo races, gendering, fusions, release schedule |
|
||||
| [ECONOMIC_SYSTEMS.md](games/age-of-four/docs/ECONOMIC_SYSTEMS.md) | Economy, civic axis, capitalism cascade |
|
||||
| [GOVERNMENTS.md](games/age-of-four/docs/GOVERNMENTS.md) | Government types and mechanics |
|
||||
| [GLOSSARY.md](games/age-of-four/docs/GLOSSARY.md) | Term definitions |
|
||||
| COMBAT_SYSTEM.md | Combat mechanics (written when M8 is built) |
|
||||
| SPELL_SYSTEM.md | Spell/mana system (written when M9 is built) |
|
||||
| ERA_SYSTEM.md | Era progression (written when needed) |
|
||||
|
||||
### Build Process (`.project/`)
|
||||
|
||||
| Doc | Covers |
|
||||
|-----|--------|
|
||||
| [ROADMAP.md](.project/ROADMAP.md) | Demo scope + 12-phase build sequence |
|
||||
| [FEATURE_GAP.md](.project/FEATURE_GAP.md) | New systems beyond the reference implementation |
|
||||
| `tasks/m0-m4/` | Milestone task lists with porting checklists |
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
./run play # Launch the game
|
||||
./run editor # Open Godot editor
|
||||
./run lint # gdlint engine/src/
|
||||
./run test # GUT tests headless
|
||||
pnpm dev # Serve the player guide
|
||||
```
|
||||
BIN
after-equator.png
Normal file
|
After Width: | Height: | Size: 78 KiB |
BIN
after-npole.png
Normal file
|
After Width: | Height: | Size: 82 KiB |
BIN
after-spole.png
Normal file
|
After Width: | Height: | Size: 84 KiB |
BIN
current-npole-check.png
Normal file
|
After Width: | Height: | Size: 71 KiB |
BIN
direct-nav-npole.png
Normal file
|
After Width: | Height: | Size: 83 KiB |
BIN
equator-view.png
Normal file
|
After Width: | Height: | Size: 65 KiB |
240
export_presets.cfg
Normal file
|
|
@ -0,0 +1,240 @@
|
|||
[preset.0]
|
||||
|
||||
name="Windows Desktop"
|
||||
platform="Windows Desktop"
|
||||
runnable=true
|
||||
dedicated_server=false
|
||||
custom_features=""
|
||||
export_filter="all_resources"
|
||||
include_filter=""
|
||||
exclude_filter=""
|
||||
export_path="builds/windows/MagicCivilization.exe"
|
||||
encryption_include_filters=""
|
||||
encryption_exclude_filters=""
|
||||
encrypt_pck=false
|
||||
encrypt_directory=false
|
||||
|
||||
[preset.0.options]
|
||||
|
||||
custom_template/debug=""
|
||||
custom_template/release=""
|
||||
debug/export_console_wrapper=1
|
||||
binary_format/embed_pck=true
|
||||
texture_format/bptc=false
|
||||
texture_format/s3tc=true
|
||||
texture_format/etc=false
|
||||
texture_format/etc2=false
|
||||
codesign/enable=false
|
||||
application/modify_resources=false
|
||||
application/icon=""
|
||||
application/console_wrapper_icon=""
|
||||
application/icon_interpolation=4
|
||||
application/file_version=""
|
||||
application/product_version=""
|
||||
application/company_name="Magic Civilization"
|
||||
application/product_name="Magic Civilization"
|
||||
application/file_description="Fantasy 4X Strategy"
|
||||
application/copyright=""
|
||||
application/trademarks=""
|
||||
application/export_angle=0
|
||||
ssh_remote_deploy/enabled=false
|
||||
|
||||
[preset.1]
|
||||
|
||||
name="macOS"
|
||||
platform="macOS"
|
||||
runnable=true
|
||||
dedicated_server=false
|
||||
custom_features=""
|
||||
export_filter="all_resources"
|
||||
include_filter=""
|
||||
exclude_filter=""
|
||||
export_path="builds/macos/MagicCivilization.zip"
|
||||
encryption_include_filters=""
|
||||
encryption_exclude_filters=""
|
||||
encrypt_pck=false
|
||||
encrypt_directory=false
|
||||
|
||||
[preset.1.options]
|
||||
|
||||
custom_template/debug=""
|
||||
custom_template/release=""
|
||||
debug/export_console_wrapper=1
|
||||
binary_format/embed_pck=true
|
||||
texture_format/bptc=true
|
||||
texture_format/s3tc=true
|
||||
texture_format/etc=false
|
||||
texture_format/etc2=true
|
||||
codesign/codesign=1
|
||||
codesign/installer_identity=""
|
||||
codesign/apple_team_id=""
|
||||
codesign/identity=""
|
||||
codesign/entitlements/custom_file=""
|
||||
codesign/entitlements/allow_jit_code_execution=false
|
||||
codesign/entitlements/allow_unsigned_executable_memory=false
|
||||
codesign/entitlements/allow_dyld_environment_variables=false
|
||||
codesign/custom_options=PackedStringArray()
|
||||
notarization/notarization=0
|
||||
application/icon=""
|
||||
application/bundle_identifier="com.magicciv.game"
|
||||
application/signature=""
|
||||
application/app_category="public.app-category.games"
|
||||
application/short_version="0.1.0"
|
||||
application/version="0.1.0"
|
||||
application/copyright=""
|
||||
application/export_angle=0
|
||||
display/high_res=true
|
||||
ssh_remote_deploy/enabled=false
|
||||
|
||||
[preset.2]
|
||||
|
||||
name="Linux"
|
||||
platform="Linux/X11"
|
||||
runnable=true
|
||||
dedicated_server=false
|
||||
custom_features=""
|
||||
export_filter="all_resources"
|
||||
include_filter=""
|
||||
exclude_filter=""
|
||||
export_path="builds/linux/MagicCivilization.x86_64"
|
||||
encryption_include_filters=""
|
||||
encryption_exclude_filters=""
|
||||
encrypt_pck=false
|
||||
encrypt_directory=false
|
||||
|
||||
[preset.2.options]
|
||||
|
||||
custom_template/debug=""
|
||||
custom_template/release=""
|
||||
debug/export_console_wrapper=1
|
||||
binary_format/embed_pck=true
|
||||
texture_format/bptc=true
|
||||
texture_format/s3tc=true
|
||||
texture_format/etc=false
|
||||
texture_format/etc2=false
|
||||
ssh_remote_deploy/enabled=false
|
||||
|
||||
[preset.3]
|
||||
|
||||
name="Android"
|
||||
platform="Android"
|
||||
runnable=true
|
||||
dedicated_server=false
|
||||
custom_features=""
|
||||
export_filter="all_resources"
|
||||
include_filter=""
|
||||
exclude_filter=""
|
||||
export_path="builds/android/MagicCivilization.apk"
|
||||
encryption_include_filters=""
|
||||
encryption_exclude_filters=""
|
||||
encrypt_pck=false
|
||||
encrypt_directory=false
|
||||
|
||||
[preset.3.options]
|
||||
|
||||
custom_template/debug=""
|
||||
custom_template/release=""
|
||||
gradle_build/use_gradle_build=false
|
||||
gradle_build/compress_native_libraries=false
|
||||
gradle_build/export_format=0
|
||||
architectures/armeabi-v7a=false
|
||||
architectures/arm64-v8a=true
|
||||
architectures/x86=false
|
||||
architectures/x86_64=false
|
||||
keystore/debug="/var/home/lilith/.android/debug.keystore"
|
||||
keystore/debug_user="androiddebugkey"
|
||||
keystore/debug_password="android"
|
||||
keystore/release="/var/home/lilith/.android/debug.keystore"
|
||||
keystore/release_user="androiddebugkey"
|
||||
keystore/release_password="android"
|
||||
version/code=1
|
||||
version/name="0.1.0"
|
||||
package/unique_name="com.magicciv.game"
|
||||
package/name="Magic Civilization"
|
||||
package/signed=true
|
||||
package/classify_as_game=true
|
||||
package/retain_data_on_uninstall=false
|
||||
package/exclude_from_recents=false
|
||||
package/show_in_android_tv=false
|
||||
package/show_in_app_library=true
|
||||
package/show_as_launcher_app=false
|
||||
launcher_icons/main_192x192=""
|
||||
launcher_icons/adaptive_foreground_432x432=""
|
||||
launcher_icons/adaptive_background_432x432=""
|
||||
graphics/opengl_debug=false
|
||||
xr_features/xr_mode=0
|
||||
screen/immersive_mode=true
|
||||
screen/support_small=true
|
||||
screen/support_normal=true
|
||||
screen/support_large=true
|
||||
screen/support_xlarge=true
|
||||
user_permissions/VIBRATE=false
|
||||
texture_format/s3tc=false
|
||||
texture_format/etc=false
|
||||
texture_format/etc2=true
|
||||
|
||||
[preset.4]
|
||||
|
||||
name="iOS"
|
||||
platform="iOS"
|
||||
runnable=true
|
||||
dedicated_server=false
|
||||
custom_features=""
|
||||
export_filter="all_resources"
|
||||
include_filter=""
|
||||
exclude_filter=""
|
||||
export_path="builds/ios/MagicCivilization.xcodeproj"
|
||||
encryption_include_filters=""
|
||||
encryption_exclude_filters=""
|
||||
encrypt_pck=false
|
||||
encrypt_directory=false
|
||||
|
||||
[preset.4.options]
|
||||
|
||||
custom_template/debug=""
|
||||
custom_template/release=""
|
||||
architectures/arm64=true
|
||||
application/app_store_team_id="8SX7UH9HF4"
|
||||
application/provisioning_profile_uuid_debug=""
|
||||
application/provisioning_profile_uuid_release=""
|
||||
application/bundle_identifier="com.magicciv.game"
|
||||
application/signature=""
|
||||
application/short_version="0.1.0"
|
||||
application/version="0.1.0"
|
||||
application/icon_interpolation=4
|
||||
application/launch_screens_interpolation=4
|
||||
application/export_project_only=true
|
||||
capabilities/access_wifi=false
|
||||
capabilities/push_notifications=false
|
||||
user_data/accessible_from_files_app=false
|
||||
privacy/camera=false
|
||||
privacy/microphone=false
|
||||
privacy/photolibrary=false
|
||||
icons/iphone_120x120="res://games/age-of-four/icons/icon_120x120.png"
|
||||
icons/iphone_180x180="res://games/age-of-four/icons/icon_180x180.png"
|
||||
icons/ipad_76x76="res://games/age-of-four/icons/icon_76x76.png"
|
||||
icons/ipad_152x152="res://games/age-of-four/icons/icon_152x152.png"
|
||||
icons/ipad_167x167="res://games/age-of-four/icons/icon_167x167.png"
|
||||
icons/app_store_1024x1024="res://games/age-of-four/icons/icon_1024x1024.png"
|
||||
icons/spotlight_40x40="res://games/age-of-four/icons/icon_40x40.png"
|
||||
icons/spotlight_80x80="res://games/age-of-four/icons/icon_80x80.png"
|
||||
icons/spotlight_120x120="res://games/age-of-four/icons/icon_120x120.png"
|
||||
icons/settings_29x29="res://games/age-of-four/icons/icon_29x29.png"
|
||||
icons/settings_58x58="res://games/age-of-four/icons/icon_58x58.png"
|
||||
icons/settings_87x87="res://games/age-of-four/icons/icon_87x87.png"
|
||||
icons/notification_20x20="res://games/age-of-four/icons/icon_20x20.png"
|
||||
icons/notification_40x40="res://games/age-of-four/icons/icon_40x40.png"
|
||||
icons/notification_60x60="res://games/age-of-four/icons/icon_60x60.png"
|
||||
icons/notification_76x76="res://games/age-of-four/icons/icon_76x76.png"
|
||||
icons/notification_114x114="res://games/age-of-four/icons/icon_114x114.png"
|
||||
storyboard/use_custom_storyboard=false
|
||||
storyboard/custom_storyboard=""
|
||||
storyboard/image_scale_mode=0
|
||||
storyboard/use_custom_bg_color=false
|
||||
storyboard/custom_bg_color=Color(0, 0, 0, 1)
|
||||
icons/ios_128x128="res://games/age-of-four/icons/icon_128x128.png"
|
||||
icons/ios_136x136="res://games/age-of-four/icons/icon_136x136.png"
|
||||
icons/ios_192x192="res://games/age-of-four/icons/icon_192x192.png"
|
||||
texture_format/s3tc=false
|
||||
texture_format/etc=false
|
||||
texture_format/etc2=true
|
||||
5
gdformatrc
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# lilith-gdtoolkit-config: GDScript Formatting Configuration
|
||||
# Sync to project: lilith-gdtoolkit-sync
|
||||
# Check for drift: lilith-gdtoolkit-sync --check
|
||||
|
||||
line_length: 100
|
||||
61
gdlintrc
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
# lilith-gdtoolkit-config: GDScript Linting Configuration
|
||||
# Sync to project: lilith-gdtoolkit-sync
|
||||
# Check for drift: lilith-gdtoolkit-sync --check
|
||||
|
||||
class-definitions-order:
|
||||
- tools
|
||||
- classnames
|
||||
- extends
|
||||
- docstrings
|
||||
- signals
|
||||
- enums
|
||||
- consts
|
||||
- staticvars
|
||||
- exports
|
||||
- pubvars
|
||||
- prvvars
|
||||
- onreadypubvars
|
||||
- onreadyprvvars
|
||||
- others
|
||||
|
||||
# Naming conventions (GDScript standard)
|
||||
class-name: ([A-Z][a-z0-9]*)+
|
||||
class-variable-name: _?[a-z][a-z0-9]*(_[a-z0-9]+)*
|
||||
class-load-variable-name: (([A-Z][a-z0-9]*)+|_?[a-z][a-z0-9]*(_[a-z0-9]+)*)
|
||||
constant-name: _?[A-Z][A-Z0-9]*(_[A-Z0-9]+)*
|
||||
enum-name: ([A-Z][a-z0-9]*)+
|
||||
enum-element-name: '[A-Z][A-Z0-9]*(_[A-Z0-9]+)*'
|
||||
function-name: (_on_([A-Z][a-z0-9]*)+(_[a-z0-9]+)*|_?[a-z][a-z0-9]*(_[a-z0-9]+)*)
|
||||
function-argument-name: _?[a-z][a-z0-9]*(_[a-z0-9]+)*
|
||||
function-preload-variable-name: ([A-Z][a-z0-9]*)+
|
||||
function-variable-name: '[a-z][a-z0-9]*(_[a-z0-9]+)*'
|
||||
load-constant-name: (([A-Z][a-z0-9]*)+|_?[A-Z][A-Z0-9]*(_[A-Z0-9]+)*)
|
||||
loop-variable-name: _?[a-z][a-z0-9]*(_[a-z0-9]+)*
|
||||
signal-name: '[a-z][a-z0-9]*(_[a-z0-9]+)*'
|
||||
sub-class-name: _?([A-Z][a-z0-9]*)+
|
||||
|
||||
# Limits (aligned with Lilith ecosystem standards)
|
||||
max-line-length: 100
|
||||
max-file-lines: 500
|
||||
max-public-methods: 20
|
||||
max-returns: 6
|
||||
function-arguments-number: 10
|
||||
|
||||
# Indentation: tabs (GDScript convention)
|
||||
tab-characters: 1
|
||||
|
||||
# Enabled checks
|
||||
# trailing-whitespace, unnecessary-pass, mixed-tabs-and-spaces are active (not null)
|
||||
|
||||
# Disabled checks
|
||||
comparison-with-itself: null
|
||||
duplicated-load: null
|
||||
expression-not-assigned: null
|
||||
no-elif-return: null
|
||||
no-else-return: null
|
||||
unused-argument: null
|
||||
|
||||
# Exclusions
|
||||
disable: []
|
||||
excluded_directories: !!set
|
||||
.git: null
|
||||
BIN
m1_base_turn200.png
Normal file
|
After Width: | Height: | Size: 84 KiB |
BIN
m1_fixed_sim.png
Normal file
|
After Width: | Height: | Size: 82 KiB |
BIN
m1_guide_climate_sim.png
Normal file
|
After Width: | Height: | Size: 80 KiB |
BIN
npole-view.png
Normal file
|
After Width: | Height: | Size: 64 KiB |
4
pnpm-workspace.yaml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
packages:
|
||||
- packages/*
|
||||
- guide/engine
|
||||
- guide/age-of-four
|
||||
36
project.godot
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
; Engine configuration file.
|
||||
; It's best edited using the editor UI and not directly,
|
||||
; but it can also be manually edited if needed.
|
||||
|
||||
config_version=5
|
||||
|
||||
[application]
|
||||
|
||||
config/name="Magic Civilization"
|
||||
run/main_scene="res://engine/scenes/main/main.tscn"
|
||||
config/features=PackedStringArray("4.3", "GL Compatibility")
|
||||
|
||||
[autoload]
|
||||
|
||||
EnvConfig="*res://engine/src/autoloads/env_config.gd"
|
||||
SettingsManager="*res://engine/src/autoloads/settings_manager.gd"
|
||||
EventBus="*res://engine/src/autoloads/event_bus.gd"
|
||||
DataLoader="*res://engine/src/autoloads/data_loader.gd"
|
||||
ThemeVocabulary="*res://engine/src/autoloads/theme_vocabulary.gd"
|
||||
ThemeAssets="*res://engine/src/autoloads/theme_assets.gd"
|
||||
GameLogger="*res://engine/src/autoloads/game_logger.gd"
|
||||
GameState="*res://engine/src/autoloads/game_state.gd"
|
||||
TurnManager="*res://engine/src/autoloads/turn_manager.gd"
|
||||
ScreenCapture="*res://engine/scenes/tests/capture_screenshot.gd"
|
||||
|
||||
[display]
|
||||
|
||||
window/size/viewport_width=1920
|
||||
window/size/viewport_height=1080
|
||||
window/stretch/mode="canvas_items"
|
||||
|
||||
[rendering]
|
||||
|
||||
renderer/rendering_method="gl_compatibility"
|
||||
textures/vram_compression/import_etc2_astc=true
|
||||
environment/defaults/default_clear_color=Color(0, 0, 0, 1)
|
||||
684
run
Executable file
|
|
@ -0,0 +1,684 @@
|
|||
#!/usr/bin/env bash
|
||||
# Task runner for Magic Civilization
|
||||
# Usage: ./run <command> [args...]
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
cd "$SCRIPT_DIR"
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
DIM='\033[2m'
|
||||
NC='\033[0m'
|
||||
|
||||
GODOT_BIN="flatpak run --user org.godotengine.Godot"
|
||||
|
||||
usage() {
|
||||
echo -e "${BLUE}Magic Civilization${NC} — Task Runner"
|
||||
echo ""
|
||||
echo "Usage: ./run <command> [args...]"
|
||||
echo ""
|
||||
echo -e "${YELLOW}Development${NC}"
|
||||
echo " play Launch the game"
|
||||
echo " editor Open Godot editor"
|
||||
echo " lint Run gdlint on scripts/"
|
||||
echo " format Run gdformat on scripts/"
|
||||
echo " test Run GUT tests (GDScript) + vitest (guide)"
|
||||
echo " verify lint + typecheck + test (full pipeline)"
|
||||
echo " screenshot [name] [scene] [delay] Capture screenshot"
|
||||
echo ""
|
||||
echo -e "${YELLOW}Export${NC}"
|
||||
echo " export [version] Export all platforms (parallel)"
|
||||
echo " export:windows [version] Export Windows only"
|
||||
echo " export:macos [version] Export macOS only"
|
||||
echo " export:linux [version] Export Linux only"
|
||||
echo " export:android [version] Export Android APK"
|
||||
echo " export:ios [version] Export iOS Xcode project"
|
||||
echo ""
|
||||
echo -e "${YELLOW}Install (deploy to target)${NC}"
|
||||
echo " install osx [version] Export + install .app on plum"
|
||||
echo " install --dev osx [ver] Debug build with dev config"
|
||||
echo " install iphone [version] Export + build + deploy to iPhone via plum"
|
||||
echo " install sim [version] Export + build + deploy to iOS Simulator"
|
||||
echo " install android [ver] Export + install APK via adb"
|
||||
echo ""
|
||||
echo -e "${YELLOW}Remote${NC}"
|
||||
echo " start osx Launch installed app on plum"
|
||||
echo " start ios Launch app on connected iPhone"
|
||||
echo " stop osx Kill running app on plum"
|
||||
echo ""
|
||||
echo -e "${YELLOW}Test${NC}"
|
||||
echo " smoke osx Full smoke test (export → ship → launch → screenshot)"
|
||||
echo ""
|
||||
echo -e "${YELLOW}Tools${NC}"
|
||||
echo " tools spritegen <cmd> Sprite generation pipeline (scan, status, generate, poll, review, install)"
|
||||
echo ""
|
||||
echo -e "${YELLOW}Setup${NC}"
|
||||
echo " setup Install/verify all dev dependencies"
|
||||
echo " setup --skip-templates Setup without downloading export templates"
|
||||
}
|
||||
|
||||
cmd_play() {
|
||||
echo -e "${BLUE}Launching Magic Civilization...${NC}"
|
||||
WAYLAND_DISPLAY="${WAYLAND_DISPLAY:-wayland-0}" \
|
||||
XDG_RUNTIME_DIR="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}" \
|
||||
$GODOT_BIN --rendering-method gl_compatibility "$@"
|
||||
}
|
||||
|
||||
cmd_editor() {
|
||||
echo -e "${BLUE}Opening Godot editor...${NC}"
|
||||
WAYLAND_DISPLAY="${WAYLAND_DISPLAY:-wayland-0}" \
|
||||
XDG_RUNTIME_DIR="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}" \
|
||||
$GODOT_BIN -e --rendering-method gl_compatibility "$@" &
|
||||
}
|
||||
|
||||
cmd_lint() {
|
||||
echo -e "${BLUE}Checking gdtoolkit config sync...${NC}"
|
||||
lilith-gdtoolkit-sync --check || {
|
||||
echo -e "${YELLOW}Config drift detected — syncing...${NC}"
|
||||
lilith-gdtoolkit-sync
|
||||
}
|
||||
echo -e "${BLUE}Linting engine/src/...${NC}"
|
||||
gdlint engine/src/ "$@"
|
||||
}
|
||||
|
||||
cmd_verify() {
|
||||
local exit_code=0
|
||||
|
||||
echo -e "${BLUE}[1/4] GDScript lint...${NC}"
|
||||
gdlint engine/src/ || exit_code=$?
|
||||
|
||||
echo ""
|
||||
echo -e "${BLUE}[2/4] Guide typecheck...${NC}"
|
||||
pnpm --prefix guide/age-of-four typecheck || exit_code=$?
|
||||
|
||||
echo ""
|
||||
echo -e "${BLUE}[3/4] Transpiler check (GDScript → TS sync)...${NC}"
|
||||
uv run tools/transpile-engine/transpile.py --check || exit_code=$?
|
||||
|
||||
echo ""
|
||||
echo -e "${BLUE}[4/4] Tests (GUT + vitest)...${NC}"
|
||||
cmd_test || exit_code=$?
|
||||
|
||||
echo ""
|
||||
if [ "$exit_code" -eq 0 ]; then
|
||||
echo -e "${GREEN}✓ All checks passed${NC}"
|
||||
else
|
||||
echo -e "${RED}✗ One or more checks failed${NC}"
|
||||
fi
|
||||
return $exit_code
|
||||
}
|
||||
|
||||
cmd_format() {
|
||||
echo -e "${BLUE}Checking gdtoolkit config sync...${NC}"
|
||||
lilith-gdtoolkit-sync --check || {
|
||||
echo -e "${YELLOW}Config drift detected — syncing...${NC}"
|
||||
lilith-gdtoolkit-sync
|
||||
}
|
||||
echo -e "${BLUE}Formatting engine/src/...${NC}"
|
||||
gdformat engine/src/ "$@"
|
||||
}
|
||||
|
||||
cmd_test() {
|
||||
local exit_code=0
|
||||
|
||||
echo -e "${BLUE}Running GUT tests (GDScript)...${NC}"
|
||||
WAYLAND_DISPLAY="${WAYLAND_DISPLAY:-wayland-0}" \
|
||||
XDG_RUNTIME_DIR="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}" \
|
||||
$GODOT_BIN --headless --script res://addons/gut/gut_cmdln.gd \
|
||||
-gexit "$@" || exit_code=$?
|
||||
|
||||
echo ""
|
||||
echo -e "${BLUE}Running vitest (guide)...${NC}"
|
||||
pnpm --prefix guide/age-of-four test || exit_code=$?
|
||||
|
||||
return $exit_code
|
||||
}
|
||||
|
||||
cmd_screenshot() {
|
||||
./tools/screenshot.sh "$@"
|
||||
}
|
||||
|
||||
cmd_export() {
|
||||
./tools/export.sh "$@"
|
||||
}
|
||||
|
||||
cmd_export_single() {
|
||||
local platform="$1"
|
||||
shift
|
||||
./tools/export-single.sh "$platform" "$@"
|
||||
}
|
||||
|
||||
cmd_install_osx() {
|
||||
local DEV_MODE=false
|
||||
local VERSION=""
|
||||
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--dev) DEV_MODE=true ;;
|
||||
*) VERSION="$arg" ;;
|
||||
esac
|
||||
done
|
||||
VERSION="${VERSION:-$(date +%Y%m%d_%H%M%S)}"
|
||||
|
||||
local PLUM="plum"
|
||||
local REMOTE_APP_DIR="/Applications"
|
||||
local APP_NAME="Magic Civilization.app"
|
||||
local ZIP_NAME="MagicCivilization.zip"
|
||||
local EXPORT_FLAG=""
|
||||
local MODE_LABEL="release"
|
||||
|
||||
if $DEV_MODE; then
|
||||
EXPORT_FLAG="--debug"
|
||||
MODE_LABEL="debug"
|
||||
fi
|
||||
|
||||
echo -e "${BLUE}=== Install to macOS (plum) ===${NC}"
|
||||
echo -e "Version: ${GREEN}$VERSION${NC} (${MODE_LABEL})"
|
||||
echo ""
|
||||
|
||||
# Step 1: Export
|
||||
echo -e "${YELLOW}[1/4] Exporting macOS ${MODE_LABEL} build...${NC}"
|
||||
if ! ./tools/export-single.sh macos "$VERSION" $EXPORT_FLAG 2>&1; then
|
||||
echo -e "${RED}Export failed.${NC}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
BUILD_ZIP="builds/$VERSION/macos/$ZIP_NAME"
|
||||
if [ ! -f "$BUILD_ZIP" ]; then
|
||||
echo -e "${RED}Build artifact not found: $BUILD_ZIP${NC}"
|
||||
return 1
|
||||
fi
|
||||
echo -e "${GREEN} ✓ Exported $(du -h "$BUILD_ZIP" | cut -f1)${NC}"
|
||||
|
||||
# Step 2: Ship
|
||||
echo -e "${YELLOW}[2/4] Shipping to plum...${NC}"
|
||||
if ! ssh -o ConnectTimeout=5 "$PLUM" "echo ok" >/dev/null 2>&1; then
|
||||
echo -e "${RED}Cannot reach plum via SSH${NC}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
scp "$BUILD_ZIP" "$PLUM:/tmp/$ZIP_NAME"
|
||||
echo -e "${GREEN} ✓ Uploaded${NC}"
|
||||
|
||||
# Step 3: Install
|
||||
echo -e "${YELLOW}[3/4] Installing on plum...${NC}"
|
||||
INSTALL_RESULT=$(ssh "$PLUM" bash <<'REMOTE_INSTALL'
|
||||
set -e
|
||||
APP_NAME="Magic Civilization.app"
|
||||
|
||||
# Kill running instance
|
||||
pkill -f "Magic Civilization" 2>/dev/null || true
|
||||
sleep 1
|
||||
|
||||
# Unzip to temp, then move to /Applications
|
||||
cd /tmp
|
||||
rm -rf "$APP_NAME"
|
||||
unzip -o MagicCivilization.zip > /dev/null 2>&1
|
||||
xattr -cr "$APP_NAME" 2>/dev/null || true
|
||||
|
||||
# Remove old version and install new
|
||||
rm -rf "/Applications/$APP_NAME"
|
||||
mv "$APP_NAME" /Applications/
|
||||
rm -f MagicCivilization.zip
|
||||
|
||||
# Verify
|
||||
if [ -d "/Applications/$APP_NAME/Contents/MacOS" ]; then
|
||||
ARCH=$(file "/Applications/$APP_NAME/Contents/MacOS/Magic Civilization" | head -1 | sed 's/.*: //')
|
||||
echo "INSTALLED:$ARCH"
|
||||
else
|
||||
echo "INSTALL_FAIL"
|
||||
fi
|
||||
REMOTE_INSTALL
|
||||
)
|
||||
|
||||
if [[ "$INSTALL_RESULT" == INSTALLED:* ]]; then
|
||||
echo -e "${GREEN} ✓ Installed to /Applications/${NC}"
|
||||
echo -e "${DIM} ${INSTALL_RESULT#INSTALLED:}${NC}"
|
||||
else
|
||||
echo -e "${RED} ✗ Installation failed${NC}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Step 3b: Deploy env config
|
||||
local RESOURCES_DIR="/Applications/$APP_NAME/Contents/Resources"
|
||||
if $DEV_MODE; then
|
||||
echo -e "${YELLOW} Deploying .env.development...${NC}"
|
||||
scp .env.development "$PLUM:$RESOURCES_DIR/.env.development"
|
||||
# Also ship .env.production as base .env
|
||||
scp .env.production "$PLUM:$RESOURCES_DIR/.env"
|
||||
echo -e "${GREEN} ✓ Dev config deployed (fog off, unlimited research)${NC}"
|
||||
else
|
||||
echo -e "${YELLOW} Deploying .env.production...${NC}"
|
||||
scp .env.production "$PLUM:$RESOURCES_DIR/.env"
|
||||
# Remove any leftover dev config
|
||||
ssh "$PLUM" "rm -f '$RESOURCES_DIR/.env.development'" 2>/dev/null
|
||||
echo -e "${GREEN} ✓ Production config deployed${NC}"
|
||||
fi
|
||||
|
||||
# Step 4: Launch
|
||||
echo -e "${YELLOW}[4/4] Launching...${NC}"
|
||||
ssh "$PLUM" 'open "/Applications/Magic Civilization.app"' 2>/dev/null &
|
||||
|
||||
LAUNCH_PID=$(ssh "$PLUM" bash <<'REMOTE_CHECK'
|
||||
for i in $(seq 1 10); do
|
||||
PID=$(pgrep -f "Magic Civilization" 2>/dev/null | head -1)
|
||||
if [ -n "$PID" ]; then
|
||||
echo "$PID"
|
||||
exit 0
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
REMOTE_CHECK
|
||||
)
|
||||
|
||||
if [ -n "$LAUNCH_PID" ]; then
|
||||
echo -e "${GREEN} ✓ Running (PID $LAUNCH_PID)${NC}"
|
||||
else
|
||||
echo -e "${YELLOW} ! Launched but could not confirm PID${NC}"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo -e "${GREEN}Installed and running on plum.${NC}"
|
||||
echo -e " App: /Applications/$APP_NAME"
|
||||
echo -e " Build: builds/$VERSION/macos/"
|
||||
}
|
||||
|
||||
cmd_install_ios() {
|
||||
local TARGET="$1" # "iphone" or "sim"
|
||||
shift
|
||||
local DEV_MODE=false
|
||||
local VERSION=""
|
||||
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--dev) DEV_MODE=true ;;
|
||||
*) VERSION="$arg" ;;
|
||||
esac
|
||||
done
|
||||
VERSION="${VERSION:-$(date +%Y%m%d_%H%M%S)}"
|
||||
|
||||
local PLUM="plum"
|
||||
local EXPORT_FLAG=""
|
||||
local MODE_LABEL="release"
|
||||
local XCODE_CONFIG="Release"
|
||||
|
||||
if $DEV_MODE; then
|
||||
EXPORT_FLAG="--debug"
|
||||
MODE_LABEL="debug"
|
||||
XCODE_CONFIG="Debug"
|
||||
fi
|
||||
|
||||
echo -e "${BLUE}=== Install to iOS ($TARGET) via plum ===${NC}"
|
||||
echo -e "Version: ${GREEN}$VERSION${NC} (${MODE_LABEL})"
|
||||
echo ""
|
||||
|
||||
# Step 1: Export Xcode project
|
||||
echo -e "${YELLOW}[1/4] Exporting iOS Xcode project...${NC}"
|
||||
if ! ./tools/export-single.sh ios "$VERSION" $EXPORT_FLAG 2>&1; then
|
||||
echo -e "${RED}Export failed.${NC}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local BUILD_DIR="builds/$VERSION/ios"
|
||||
if [ ! -d "$BUILD_DIR/MagicCivilization.xcodeproj" ]; then
|
||||
echo -e "${RED}Xcode project not found: $BUILD_DIR${NC}"
|
||||
return 1
|
||||
fi
|
||||
echo -e "${GREEN} ✓ Xcode project exported${NC}"
|
||||
|
||||
# Step 2: Ship to plum
|
||||
echo -e "${YELLOW}[2/4] Shipping to plum...${NC}"
|
||||
if ! ssh -o ConnectTimeout=5 "$PLUM" "echo ok" >/dev/null 2>&1; then
|
||||
echo -e "${RED}Cannot reach plum via SSH${NC}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local REMOTE_BUILD="~/MagicCiv_iOS_Build"
|
||||
ssh "$PLUM" "rm -rf $REMOTE_BUILD && mkdir -p $REMOTE_BUILD"
|
||||
rsync -az --progress "$BUILD_DIR/" "$PLUM:$REMOTE_BUILD/" 2>&1 | tail -3
|
||||
echo -e "${GREEN} ✓ Shipped to plum${NC}"
|
||||
|
||||
# Step 3: Build with xcodebuild
|
||||
echo -e "${YELLOW}[3/4] Building on plum with xcodebuild...${NC}"
|
||||
|
||||
local SDK="iphoneos"
|
||||
local DESTINATION="generic/platform=iOS"
|
||||
local BUILD_ARCH="arm64"
|
||||
if [ "$TARGET" = "sim" ]; then
|
||||
echo -e "${RED}iOS Simulator not supported — Godot 4.6.1 templates lack arm64 simulator slices.${NC}"
|
||||
echo -e "Use ${GREEN}./run install iphone${NC} for device deployment instead."
|
||||
return 1
|
||||
fi
|
||||
|
||||
BUILD_RESULT=$(ssh "$PLUM" bash <<REMOTE_BUILD_CMD
|
||||
set -e
|
||||
cd ~/MagicCiv_iOS_Build
|
||||
|
||||
# Build without code signing (sign separately after)
|
||||
xcodebuild \
|
||||
-project MagicCivilization.xcodeproj \
|
||||
-scheme MagicCivilization \
|
||||
-configuration $XCODE_CONFIG \
|
||||
-sdk $SDK \
|
||||
-destination "$DESTINATION" \
|
||||
CODE_SIGN_IDENTITY="" \
|
||||
CODE_SIGNING_REQUIRED=NO \
|
||||
CODE_SIGNING_ALLOWED=NO \
|
||||
ARCHS="$BUILD_ARCH" \
|
||||
ONLY_ACTIVE_ARCH=NO \
|
||||
build 2>&1 | tail -5
|
||||
|
||||
if [ \${PIPESTATUS[0]} -eq 0 ]; then
|
||||
echo "BUILD_OK"
|
||||
else
|
||||
echo "BUILD_FAIL"
|
||||
fi
|
||||
REMOTE_BUILD_CMD
|
||||
)
|
||||
|
||||
if ! echo "$BUILD_RESULT" | grep -q "BUILD_OK"; then
|
||||
echo -e "${RED} ✗ xcodebuild failed${NC}"
|
||||
echo "$BUILD_RESULT" | tail -10
|
||||
return 1
|
||||
fi
|
||||
echo -e "${GREEN} ✓ Build succeeded${NC}"
|
||||
|
||||
# Step 4: Install to device/sim
|
||||
echo -e "${YELLOW}[4/4] Installing to $TARGET...${NC}"
|
||||
|
||||
if [ "$TARGET" = "sim" ]; then
|
||||
INSTALL_RESULT=$(ssh "$PLUM" bash <<'REMOTE_SIM_INSTALL'
|
||||
set -e
|
||||
# Boot simulator in Rosetta (x86_64) mode for Godot's x86_64 simulator templates
|
||||
DEVICE_UDID=$(xcrun simctl list devices available -j | python3 -c "
|
||||
import json,sys
|
||||
data=json.load(sys.stdin)
|
||||
for runtime,devices in data.get('devices',{}).items():
|
||||
if 'iOS' in runtime:
|
||||
for d in devices:
|
||||
if 'iPhone' in d['name'] and d['isAvailable']:
|
||||
print(d['udid']); sys.exit(0)
|
||||
" 2>/dev/null)
|
||||
|
||||
if [ -z "$DEVICE_UDID" ]; then
|
||||
echo "NO_SIMULATOR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
xcrun simctl boot "$DEVICE_UDID" 2>/dev/null || true
|
||||
open -a Simulator 2>/dev/null || true
|
||||
sleep 2
|
||||
|
||||
# Find the built .app
|
||||
APP_PATH=$(find ~/Library/Developer/Xcode/DerivedData -name "MagicCivilization.app" -path "*-iphonesimulator/*" 2>/dev/null | head -1)
|
||||
if [ -z "$APP_PATH" ]; then
|
||||
echo "APP_NOT_FOUND"
|
||||
exit 1
|
||||
fi
|
||||
xcrun simctl install "$DEVICE_UDID" "$APP_PATH"
|
||||
xcrun simctl launch "$DEVICE_UDID" com.magicciv.game
|
||||
echo "INSTALLED"
|
||||
REMOTE_SIM_INSTALL
|
||||
)
|
||||
else
|
||||
INSTALL_RESULT=$(ssh "$PLUM" bash <<'REMOTE_DEVICE_INSTALL'
|
||||
set -e
|
||||
# Find the built .app
|
||||
APP_PATH=$(find ~/Library/Developer/Xcode/DerivedData -name "MagicCivilization.app" -path "*/Release-iphoneos/*" -o -name "MagicCivilization.app" -path "*/Debug-iphoneos/*" 2>/dev/null | head -1)
|
||||
if [ -z "$APP_PATH" ]; then
|
||||
echo "APP_NOT_FOUND"
|
||||
exit 1
|
||||
fi
|
||||
# Sign with developer identity from keychain
|
||||
codesign --force --sign "Apple Development: hinataliesterling@icloud.com (X8424J5CTB)" --timestamp=none --generate-entitlement-der "$APP_PATH" 2>&1 || true
|
||||
# Install to connected device
|
||||
xcrun devicectl device install app --device "Natalie's iPhone" "$APP_PATH" 2>&1 | tail -3
|
||||
echo "INSTALLED"
|
||||
REMOTE_DEVICE_INSTALL
|
||||
)
|
||||
fi
|
||||
|
||||
if echo "$INSTALL_RESULT" | grep -q "INSTALLED"; then
|
||||
echo -e "${GREEN} ✓ Installed to $TARGET${NC}"
|
||||
else
|
||||
echo -e "${RED} ✗ Install failed${NC}"
|
||||
echo "$INSTALL_RESULT"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo -e "${GREEN}Deployed to $TARGET via plum.${NC}"
|
||||
}
|
||||
|
||||
cmd_install_android() {
|
||||
local DEV_MODE=false
|
||||
local VERSION=""
|
||||
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--dev) DEV_MODE=true ;;
|
||||
*) VERSION="$arg" ;;
|
||||
esac
|
||||
done
|
||||
VERSION="${VERSION:-$(date +%Y%m%d_%H%M%S)}"
|
||||
|
||||
local EXPORT_FLAG=""
|
||||
local MODE_LABEL="release"
|
||||
|
||||
if $DEV_MODE; then
|
||||
EXPORT_FLAG="--debug"
|
||||
MODE_LABEL="debug"
|
||||
fi
|
||||
|
||||
local ADB="$HOME/Android/Sdk/platform-tools/adb"
|
||||
|
||||
echo -e "${BLUE}=== Install to Android ===${NC}"
|
||||
echo -e "Version: ${GREEN}$VERSION${NC} (${MODE_LABEL})"
|
||||
echo ""
|
||||
|
||||
# Step 1: Export
|
||||
echo -e "${YELLOW}[1/3] Exporting Android APK...${NC}"
|
||||
if ! ./tools/export-single.sh android "$VERSION" $EXPORT_FLAG 2>&1; then
|
||||
echo -e "${RED}Export failed.${NC}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local APK="builds/$VERSION/android/MagicCivilization.apk"
|
||||
if [ ! -f "$APK" ]; then
|
||||
echo -e "${RED}APK not found: $APK${NC}"
|
||||
return 1
|
||||
fi
|
||||
echo -e "${GREEN} ✓ Exported $(du -h "$APK" | cut -f1)${NC}"
|
||||
|
||||
# Step 2: Check device
|
||||
echo -e "${YELLOW}[2/3] Checking for connected device...${NC}"
|
||||
if ! "$ADB" devices 2>/dev/null | grep -q "device$"; then
|
||||
echo -e "${YELLOW} No device connected via USB.${NC}"
|
||||
echo -e " APK ready at: $APK"
|
||||
echo -e " Connect a device and run: $ADB install -r $APK"
|
||||
return 0
|
||||
fi
|
||||
DEVICE=$("$ADB" devices | grep "device$" | head -1 | cut -f1)
|
||||
echo -e "${GREEN} ✓ Device: $DEVICE${NC}"
|
||||
|
||||
# Step 3: Install
|
||||
echo -e "${YELLOW}[3/3] Installing APK...${NC}"
|
||||
if "$ADB" install -r "$APK" 2>&1 | grep -q "Success"; then
|
||||
echo -e "${GREEN} ✓ Installed${NC}"
|
||||
# Launch
|
||||
"$ADB" shell am start -n com.magicciv.game/com.godot.game.GodotApp 2>/dev/null
|
||||
echo -e "${GREEN} ✓ Launched${NC}"
|
||||
else
|
||||
echo -e "${RED} ✗ Install failed${NC}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo -e "${GREEN}Deployed to Android.${NC}"
|
||||
}
|
||||
|
||||
cmd_start_osx() {
|
||||
local PLUM="plum"
|
||||
if ! ssh -o ConnectTimeout=5 "$PLUM" "echo ok" >/dev/null 2>&1; then
|
||||
echo -e "${RED}Cannot reach plum via SSH${NC}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Check if already running
|
||||
EXISTING=$(ssh "$PLUM" 'pgrep -f "Magic Civilization" 2>/dev/null | head -1')
|
||||
if [ -n "$EXISTING" ]; then
|
||||
echo -e "${YELLOW}Already running (PID $EXISTING)${NC}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Check if installed
|
||||
if ! ssh "$PLUM" '[ -d "/Applications/Magic Civilization.app" ]'; then
|
||||
echo -e "${RED}Not installed. Run: ./run install osx${NC}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
ssh "$PLUM" 'open "/Applications/Magic Civilization.app"' 2>/dev/null
|
||||
|
||||
for i in $(seq 1 10); do
|
||||
PID=$(ssh "$PLUM" 'pgrep -f "Magic Civilization" 2>/dev/null | head -1')
|
||||
if [ -n "$PID" ]; then
|
||||
echo -e "${GREEN}Running (PID $PID)${NC}"
|
||||
return 0
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
echo -e "${YELLOW}Launched but could not confirm PID${NC}"
|
||||
}
|
||||
|
||||
cmd_start_ios() {
|
||||
local PLUM="plum"
|
||||
if ! ssh -o ConnectTimeout=5 "$PLUM" "echo ok" >/dev/null 2>&1; then
|
||||
echo -e "${RED}Cannot reach plum via SSH${NC}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local DEVICE_ID="2FF5E256-27B9-5D56-89E5-B4DECCEFCE94"
|
||||
RESULT=$(ssh "$PLUM" "xcrun devicectl device process launch --device $DEVICE_ID com.magicciv.game 2>&1")
|
||||
|
||||
if echo "$RESULT" | grep -q "launched"; then
|
||||
echo -e "${GREEN}Launched on iPhone${NC}"
|
||||
else
|
||||
echo -e "${RED}Launch failed${NC}"
|
||||
echo "$RESULT" | grep -E "error:|NSLocalizedFailureReason" | head -3
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
cmd_stop_osx() {
|
||||
local PLUM="plum"
|
||||
if ! ssh -o ConnectTimeout=5 "$PLUM" "echo ok" >/dev/null 2>&1; then
|
||||
echo -e "${RED}Cannot reach plum via SSH${NC}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
PID=$(ssh "$PLUM" 'pgrep -f "Magic Civilization" 2>/dev/null | head -1')
|
||||
if [ -z "$PID" ]; then
|
||||
echo -e "${YELLOW}Not running.${NC}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
ssh "$PLUM" 'pkill -f "Magic Civilization"' 2>/dev/null
|
||||
echo -e "${GREEN}Stopped (was PID $PID)${NC}"
|
||||
}
|
||||
|
||||
cmd_smoke_osx() {
|
||||
./tools/dev/smoke-test-macos.sh "$@"
|
||||
}
|
||||
|
||||
cmd_setup() {
|
||||
./tools/dev/setup-devenv.sh "$@"
|
||||
}
|
||||
|
||||
cmd_tools_spritegen() {
|
||||
python3 tools/sprite-generation/cli.py "$@"
|
||||
}
|
||||
|
||||
# --- Route command ---
|
||||
|
||||
COMMAND="${1:-}"
|
||||
shift 2>/dev/null || true
|
||||
|
||||
case "$COMMAND" in
|
||||
play) cmd_play "$@" ;;
|
||||
editor) cmd_editor "$@" ;;
|
||||
lint) cmd_lint "$@" ;;
|
||||
verify) cmd_verify "$@" ;;
|
||||
format) cmd_format "$@" ;;
|
||||
test) cmd_test "$@" ;;
|
||||
screenshot) cmd_screenshot "$@" ;;
|
||||
export) cmd_export "$@" ;;
|
||||
export:windows) cmd_export_single windows "$@" ;;
|
||||
export:macos) cmd_export_single macos "$@" ;;
|
||||
export:linux) cmd_export_single linux "$@" ;;
|
||||
export:android) cmd_export_single android "$@" ;;
|
||||
export:ios) cmd_export_single ios "$@" ;;
|
||||
install)
|
||||
# Parse flags and target from remaining args
|
||||
INSTALL_FLAGS=""
|
||||
TARGET=""
|
||||
INSTALL_ARGS=()
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--dev) INSTALL_FLAGS="$INSTALL_FLAGS --dev" ;;
|
||||
osx|macos) TARGET="osx" ;;
|
||||
iphone) TARGET="iphone" ;;
|
||||
ipad) TARGET="iphone" ;;
|
||||
sim) TARGET="sim" ;;
|
||||
android) TARGET="android" ;;
|
||||
*) INSTALL_ARGS+=("$arg") ;;
|
||||
esac
|
||||
done
|
||||
case "${TARGET:-}" in
|
||||
osx) cmd_install_osx $INSTALL_FLAGS "${INSTALL_ARGS[@]+"${INSTALL_ARGS[@]}"}" ;;
|
||||
iphone) cmd_install_ios iphone $INSTALL_FLAGS "${INSTALL_ARGS[@]+"${INSTALL_ARGS[@]}"}" ;;
|
||||
sim) cmd_install_ios sim $INSTALL_FLAGS "${INSTALL_ARGS[@]+"${INSTALL_ARGS[@]}"}" ;;
|
||||
android) cmd_install_android $INSTALL_FLAGS "${INSTALL_ARGS[@]+"${INSTALL_ARGS[@]}"}" ;;
|
||||
*) echo -e "${RED}Unknown install target: ${TARGET:-<none>}${NC}"; echo "Available: osx, iphone, sim, android"; exit 1 ;;
|
||||
esac
|
||||
;;
|
||||
start)
|
||||
TARGET="${1:-}"
|
||||
shift 2>/dev/null || true
|
||||
case "$TARGET" in
|
||||
osx|macos) cmd_start_osx "$@" ;;
|
||||
ios|iphone) cmd_start_ios "$@" ;;
|
||||
*) echo -e "${RED}Unknown start target: $TARGET${NC}"; echo "Available: osx, ios"; exit 1 ;;
|
||||
esac
|
||||
;;
|
||||
stop)
|
||||
TARGET="${1:-}"
|
||||
shift 2>/dev/null || true
|
||||
case "$TARGET" in
|
||||
osx|macos) cmd_stop_osx "$@" ;;
|
||||
*) echo -e "${RED}Unknown stop target: $TARGET${NC}"; echo "Available: osx"; exit 1 ;;
|
||||
esac
|
||||
;;
|
||||
smoke)
|
||||
TARGET="${1:-}"
|
||||
shift 2>/dev/null || true
|
||||
case "$TARGET" in
|
||||
osx|macos) cmd_smoke_osx "$@" ;;
|
||||
*) echo -e "${RED}Unknown smoke target: $TARGET${NC}"; echo "Available: osx"; exit 1 ;;
|
||||
esac
|
||||
;;
|
||||
tools)
|
||||
TOOL="${1:-}"
|
||||
shift 2>/dev/null || true
|
||||
case "$TOOL" in
|
||||
spritegen) cmd_tools_spritegen "$@" ;;
|
||||
*) echo -e "${RED}Unknown tool: ${TOOL:-<none>}${NC}"; echo "Available: spritegen"; exit 1 ;;
|
||||
esac
|
||||
;;
|
||||
setup) cmd_setup "$@" ;;
|
||||
help|--help|-h|"") usage ;;
|
||||
*) echo -e "${RED}Unknown command: $COMMAND${NC}"; echo ""; usage; exit 1 ;;
|
||||
esac
|
||||
BIN
spole-view.png
Normal file
|
After Width: | Height: | Size: 61 KiB |
BIN
v2-equator.png
Normal file
|
After Width: | Height: | Size: 77 KiB |
BIN
v2-npole.png
Normal file
|
After Width: | Height: | Size: 72 KiB |
BIN
v2-spole.png
Normal file
|
After Width: | Height: | Size: 71 KiB |