Compare commits
42 Commits
3d406179bf
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 59b18b23bc | |||
| 6feaf84e39 | |||
| ad5445b09d | |||
| 542c0cdc19 | |||
| 6699b3b891 | |||
| b68b87d475 | |||
| fbc26bb3b8 | |||
| ec8761f4e8 | |||
| 6decf2a9d2 | |||
| 830c7aef14 | |||
| 2ad7feef96 | |||
| 6db3e60fd1 | |||
| c5688d2c0d | |||
| c149fe02a3 | |||
| 4ac8a77e8e | |||
| 670ee5c59c | |||
| e90ab07e64 | |||
| 04a72e4966 | |||
| fe1f46212b | |||
| 1eb940ef15 | |||
| 57dd5d1e36 | |||
| 7777800a5d | |||
| 4a3fd37ab4 | |||
| b939246ba4 | |||
| 251cfa5016 | |||
| 390a09015b | |||
| 3224f3768b | |||
| 171d68e102 | |||
| 33859d2cf6 | |||
| 8cf554574b | |||
| ff8ee32c9a | |||
| c38a9670ba | |||
| 672f055a80 | |||
| adf1475fc0 | |||
| 06d37aac10 | |||
| 99482c7011 | |||
| fbb7c0490c | |||
| dfe0cb3b6a | |||
| 884cc4503f | |||
| 0651603fd2 | |||
| 69ed79ce86 | |||
| e1ac56d201 |
1
.gitignore
vendored
@@ -2,3 +2,4 @@
|
||||
bin
|
||||
obj
|
||||
.idea
|
||||
.godot
|
||||
@@ -1,3 +1,7 @@
|
||||
# Linux-specific instructions
|
||||
|
||||
- After every iteration, run `dotnet jb cleanupcode --build=False '$file1' '$file2' ...` for every C# file you touched.
|
||||
## Code work
|
||||
- After every iteration, run `dotnet jb cleanupcode --verbosity:ERROR ./ReactorMaintenance.slnx`.
|
||||
|
||||
## Documentation work or planning
|
||||
- No cleanup steps necessary
|
||||
@@ -1,5 +1,6 @@
|
||||
# Platform and documentation
|
||||
|
||||
Find out which platform you're running on.
|
||||
If this is a linux environment, read `AGENTS.linux.md`.
|
||||
If this is a windows environment, read `AGENTS.windows.md`.
|
||||
Follow the guidelines laid out in `CODESTYLE.md`.
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
# Windows-specific instructions
|
||||
|
||||
## Code work
|
||||
- After the implementation is finished, run `python D:\Code\crlf.py $file1 $file2 ...` for changed files you recognize, in order to normalize all line endings of all touched files to CRLF.
|
||||
- After every iteration, run `jb cleanupcode '$file1' '$file2' ...` for every C# file you touched.
|
||||
- After every iteration, run `jb cleanupcode --verbosity:ERROR ReactorMaintenance.slnx`.
|
||||
|
||||
## Documentation work or planning
|
||||
- No cleanup steps necessary
|
||||
6
NuGet.config
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<packageSources>
|
||||
<add key="GodotSharpLocal" value="..\Godot_v4.5.1-stable_mono_win64\GodotSharp\Tools\nupkgs" />
|
||||
</packageSources>
|
||||
</configuration>
|
||||
11
README.md
@@ -1,10 +1,11 @@
|
||||
# Reactor Maintenance
|
||||
|
||||
C# WinUI 3 + Win2D level editor for the deterministic grid simulation described in `docs/design.md`.
|
||||
C# simulation with WinUI 3 + Win2D editor and a Godot frontend shell for the deterministic grid simulation described in `docs/design.md`.
|
||||
|
||||
## Projects
|
||||
|
||||
- `src/ReactorMaintenance.Simulation`: UI-independent level model, editor operations, validation, forecasts, simulation turns, versioned JSON serialization, and deterministic balancing defaults.
|
||||
- `src/ReactorMaintenance.Godot`: Godot 4.5 .NET frontend project with scene routing, UX blueprint screens, reusable UI controls, and a mock campaign manifest referencing future level JSON files.
|
||||
- `src/ReactorMaintenance.Win2D`: Win2D editor app for authoring terrain, underground fuel/coolant/electricity networks, props, explicit leak access faces, door edges, reactor consumer bindings, rule events, surface hazards, robot start, loading/saving levels, ending turns, interacting with props, and activating a ready reactor.
|
||||
- `tests/ReactorMaintenance.Simulation.Tests`: unit tests for deterministic simulation behavior, validation, serialization, and editor operations.
|
||||
|
||||
@@ -19,6 +20,8 @@ C# WinUI 3 + Win2D level editor for the deterministic grid simulation described
|
||||
## Commands
|
||||
|
||||
```powershell
|
||||
dotnet build src\ReactorMaintenance.Godot\ReactorMaintenance.Godot.csproj
|
||||
..\Godot_v4.5.1-stable_mono_win64\Godot_v4.5.1-stable_mono_win64_console.exe --headless --editor --quit --path src\ReactorMaintenance.Godot
|
||||
dotnet test tests\ReactorMaintenance.Simulation.Tests\ReactorMaintenance.Simulation.Tests.csproj
|
||||
dotnet build src\ReactorMaintenance.Win2D\ReactorMaintenance.Win2D.csproj -p:Platform=x64 -p:EnableWindowsTargeting=true
|
||||
dotnet run --project src\ReactorMaintenance.Win2D\ReactorMaintenance.Win2D.csproj -p:Platform=x64
|
||||
@@ -26,3 +29,9 @@ dotnet run --project src\ReactorMaintenance.Win2D\ReactorMaintenance.Win2D.cspro
|
||||
|
||||
The WinUI/XAML compiler is Windows-specific. On Linux, the simulation tests run normally, but the Win2D app build must be verified in a Windows-capable environment.
|
||||
|
||||
## Godot Frontend
|
||||
|
||||
The current Godot frontend is a navigable UX scaffold. It starts at a splash screen, routes through the main menu, campaign intro, random generation placeholder, level screen, win/loss overlays, options, tutorial, game over, and campaign complete screens.
|
||||
|
||||
The mock campaign manifest lives at `src\ReactorMaintenance.Godot\Data\default_campaign_manifest.json`. Each entry includes the future serialized simulation level path; those JSON files are intentionally placeholders for authored levels that will use the simulation `LevelSerializer` format.
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<Solution>
|
||||
<Folder Name="/src/">
|
||||
<Project Path="src/ReactorMaintenance.Godot/ReactorMaintenance.Godot.csproj"/>
|
||||
<Project Path="src/ReactorMaintenance.Simulation/ReactorMaintenance.Simulation.csproj"/>
|
||||
<Project Path="src/ReactorMaintenance.Win2D/ReactorMaintenance.Win2D.csproj">
|
||||
<Platform Project="x86"/>
|
||||
|
||||
223
TASKS.md
@@ -1,27 +1,204 @@
|
||||
# Reactor Maintenance Rewrite Tasks
|
||||
# Reactor Maintenance Tasks
|
||||
|
||||
## Current State
|
||||
- Approved design iteration targets the simulation model, rule removal, action economy, reactor requirements, and editor layer workflow.
|
||||
- Work is proceeding on branch `design-iteration-structural-editor` in methodical commits: docs/tasks, simulation rework, editor rework, cleanup.
|
||||
- Design documentation must preserve every existing system-level rule unless a change explicitly supersedes it. Superseded sections must document the replacement behavior with equal detail.
|
||||
This backlog tracks procedural level generation, reusable exhaustive solving, Godot random play, and Godot-assisted campaign authoring automation.
|
||||
|
||||
## Completed Work
|
||||
- Created the approved implementation plan for:
|
||||
- single multi-service consumers,
|
||||
- count-based reactor requirements,
|
||||
- cell-derived doors,
|
||||
- 0-10 structural integrity,
|
||||
- fixed automatic rule systems,
|
||||
- quick/lengthy action economy,
|
||||
- all-seeing-eye viewing without persistent unlocking,
|
||||
- layer-aware editor visualization and tools.
|
||||
## Audit Snapshot
|
||||
|
||||
## Current Work
|
||||
- Repair design documentation gaps before simulation implementation.
|
||||
- Rework simulation state, validation, serialization, forecasts, and tests.
|
||||
- Rework Win2D editor layer selection, rendering, tool filtering, drag behavior, and removed panels.
|
||||
- Current simulation tests pass: `60/60` in `tests/ReactorMaintenance.Simulation.Tests`.
|
||||
- Godot level play includes pulse step playback, terminal-gated layer controls, campaign loading, and in-game level editing with JSON save.
|
||||
- Campaign data follows the tutorial plus six-group campaign from `docs/CAMPAIGN.md`.
|
||||
- The next implementation replaces the placeholder random-level flow with seed-driven generation and reusable solver analysis.
|
||||
- Win2D is not part of this backlog.
|
||||
|
||||
## Future Work
|
||||
- Add authored sample levels once the new schema stabilizes.
|
||||
- Tune structural integrity balancing after playtesting.
|
||||
- Extend UI affordances for inspecting per-carrier consumer service state.
|
||||
## P0 Generator Contract
|
||||
|
||||
- [ ] Add a parameterized `LevelGenerationRequest`.
|
||||
- Include `Seed`, display name, optional max dimensions, `SolverDepthLimit`, target complexity, and `MaxAttempts`.
|
||||
- Require `InvolvedSystems` to contain at least one of `Fuel`, `Water`, or `Electricity`.
|
||||
- Specify exact available consumer counts per involved carrier.
|
||||
- Specify exact required consumer counts per involved carrier.
|
||||
- Specify the exact carrier set directly connected to the reactor control.
|
||||
- Reject impossible specs before generation, including required consumers greater than available consumers.
|
||||
- [ ] Add a player-facing seed profile expander.
|
||||
- Use the seed to choose involved systems.
|
||||
- Use the seed to choose available consumer counts.
|
||||
- Use the seed to choose required consumers as none, some, or all of the placed consumers.
|
||||
- Use the seed to choose how many involved systems directly connect to the reactor.
|
||||
- Expand to a concrete `LevelGenerationRequest` so random play and authoring use one generator path.
|
||||
- [ ] Generate levels without fixed archetypes.
|
||||
- Compose generated content from the requested systems, consumers, reactor connectivity, and difficulty target.
|
||||
- Avoid named campaign-group archetypes as public generation modes.
|
||||
- Let multi-system hazards emerge from generated overlapping fuel, water, electricity, heat, leak, sprinkler, and powered-prop placements.
|
||||
- [ ] Grow levels organically from the reactor.
|
||||
- Start the generated graph at the `ReactorControlProp`.
|
||||
- Grow carrier networks, branches, rooms, corridors, access routes, and player decision points outward from the reactor graph.
|
||||
- Place surrounding walls after gameplay geometry exists.
|
||||
- Calculate the occupied bounding box after generation.
|
||||
- Offset all coordinates into positive level space before creating final arrays.
|
||||
- Preserve valid wall geometry for doors, wall electricity leaks, and wall-mounted sprinkler valves.
|
||||
- [ ] Generate structural-integrity and pressure interactions.
|
||||
- Author weakened underground segments with controlled pressure or voltage exposure.
|
||||
- Generate pressure-fed and voltage-fed leaks that can be isolated, repaired, or allowed to worsen.
|
||||
- Generate hazards that restrict movement through `Unsafe` heat, electricity, or wet-electric combinations.
|
||||
- Include pressure tradeoffs from sprinkler discharge and consumer/reactor starvation.
|
||||
- Ensure generated hazards are reachable, readable, and tied to player choices.
|
||||
|
||||
## P0 Reusable Solver
|
||||
|
||||
- [ ] Add a reusable `LevelSolver` in the simulation project.
|
||||
- Accept a `LevelState` and `SolverRequest`.
|
||||
- Return a `SolverReport` with terminal counts, complexity metrics, diagnostics, and representative traces.
|
||||
- Keep the solver independent from Godot so generator, editor, tests, and future tools can share it.
|
||||
- [ ] Define solver choices as reachable lengthy actions.
|
||||
- Collapse quick movement into safe reachability from the current robot position.
|
||||
- Enumerate all reachable prop interactions, leak repairs, remedy uses, heat shield uses, and reactor activation attempts.
|
||||
- Exclude inspection, selection, terminal viewing, and individual quick movement branches from primary metrics.
|
||||
- Sort canonical choices deterministically before evaluation.
|
||||
- [ ] Evaluate every bounded branch.
|
||||
- Explore all possible choice permutations to the configured lengthy-action depth limit.
|
||||
- Count `Win`, `Lose`, and `Inconclusive` leaves.
|
||||
- Mark branches inconclusive when the depth limit or timeout is reached before terminal outcome.
|
||||
- Treat no-choice non-ready states as losing dead ends.
|
||||
- Preserve exact branch counts while using transposition caching to avoid duplicate expansion work.
|
||||
- [ ] Add solver complexity metrics.
|
||||
- Report min and max player choices to win.
|
||||
- Report min and max player choices to loss.
|
||||
- Report max and average available choices per decision state.
|
||||
- Report lose/win ratio across all bounded permutations.
|
||||
- Report inconclusive ratio.
|
||||
- Report explored node count and cache hit count.
|
||||
- Provide representative shortest winning and losing traces.
|
||||
- [ ] Parallelize branch evaluation.
|
||||
- Issue all canonical choices from a state in parallel when budget allows.
|
||||
- Wait for all choice evaluations from that state.
|
||||
- Aggregate results in canonical order for deterministic diagnostics.
|
||||
- Use ThreadPool or TPL work stealing for nested parallel branch work.
|
||||
- Respect max parallelism, timeout, and cancellation.
|
||||
- [ ] Keep solver performance and memory usage controlled.
|
||||
- Clone level states only at branch boundaries.
|
||||
- Add per-thread reusable buffers or `LevelState` clone pools where practical.
|
||||
- Avoid retaining full state graphs when only metrics are needed.
|
||||
- Keep only bounded representative traces.
|
||||
- Evaluate whether `record struct` or other value-oriented refactors reduce heap pressure without harming maintainability.
|
||||
- Add allocation or stress tests for representative generated levels.
|
||||
|
||||
## P1 Generation Acceptance And Difficulty
|
||||
|
||||
- [ ] Validate every generated candidate.
|
||||
- Run `LevelValidator`.
|
||||
- Run bounded solver analysis.
|
||||
- Reject candidates with validation errors.
|
||||
- Reject candidates with no winning branch.
|
||||
- Reject candidates whose required systems, consumer counts, or reactor-connected carrier set do not exactly match the request.
|
||||
- Reject candidates whose hazards cannot affect movement or meaningful decisions.
|
||||
- [ ] Add difficulty target filtering.
|
||||
- Filter by minimum and maximum choices to win.
|
||||
- Filter by maximum and average available choices.
|
||||
- Filter by lose/win ratio range.
|
||||
- Filter by maximum inconclusive ratio.
|
||||
- Filter by required first-choice variety.
|
||||
- Make defaults suitable for random play and configurable for campaign authoring.
|
||||
- [ ] Support campaign progression targets.
|
||||
- Early targets use fewer involved systems, lower available-choice counts, and low lose/win ratios.
|
||||
- Mid targets increase first-choice variety and introduce more losing branches.
|
||||
- Late targets allow more systems, more branching, higher lose/win ratio, and lower inconclusive tolerance.
|
||||
- Make target definitions data-driven enough for campaign automation scripts or Godot UI.
|
||||
- [ ] Add diagnostics for rejected candidates.
|
||||
- Report invalid spec reasons.
|
||||
- Report validation errors and warnings.
|
||||
- Report solver failure reasons.
|
||||
- Report mismatch against requested systems, consumers, reactor feeds, and complexity target.
|
||||
- Keep diagnostics concise enough for Godot UI and detailed enough for tests.
|
||||
|
||||
## P1 Godot Random Play And Authoring Automation
|
||||
|
||||
- [ ] Replace placeholder random-level flow.
|
||||
- Generate a level from a player seed profile.
|
||||
- Save generated JSON under `user://generated/`.
|
||||
- Load generated levels through `LevelStateLoader`.
|
||||
- Retry the same generated level from the saved JSON.
|
||||
- Complete random levels by returning to generation or regenerating from a new seed.
|
||||
- [ ] Add authoring controls to `GenerationScreen`.
|
||||
- Toggle involved systems.
|
||||
- Set available consumers per carrier.
|
||||
- Set required consumers per carrier.
|
||||
- Toggle reactor-connected carriers.
|
||||
- Set seed, max attempts, solver depth limit, and target complexity.
|
||||
- Generate, regenerate, analyze, save JSON, begin play, and return to menu.
|
||||
- [ ] Surface solver metrics in Godot.
|
||||
- Show win, lose, and inconclusive counts.
|
||||
- Show min and max choices to win.
|
||||
- Show max and average available choices.
|
||||
- Show lose/win ratio and inconclusive ratio.
|
||||
- Show concise representative trace summaries for authoring review.
|
||||
- [ ] Reuse generated JSON in Godot editor mode.
|
||||
- Open generated levels in `LevelScreen`.
|
||||
- Allow normal edit-mode changes.
|
||||
- Validate and save back to the generated JSON path.
|
||||
- Preserve generated metadata needed for retry and review.
|
||||
- [ ] Keep Godot generation responsive.
|
||||
- Run generation and solver analysis off the main UI path.
|
||||
- Show progress, cancellation, and failure diagnostics.
|
||||
- Prevent starting a stale or failed generated candidate.
|
||||
|
||||
## P1 Tests
|
||||
|
||||
- [ ] Add generator determinism tests.
|
||||
- Same request and seed serialize to identical JSON.
|
||||
- Different seeds produce meaningfully different layouts for the same request.
|
||||
- Player seed profile expansion is deterministic.
|
||||
- [ ] Add generation spec-compliance tests.
|
||||
- Generated involved systems match the request exactly.
|
||||
- Available consumer counts match the request exactly.
|
||||
- Required consumer counts match the request exactly.
|
||||
- Reactor-connected carriers match the request exactly.
|
||||
- Organic bounding and coordinate offset produce valid positive coordinates and correct array sizes.
|
||||
- [ ] Add generated hazard tests.
|
||||
- Generated pressure-fed leaks inject only when fed.
|
||||
- Structural-integrity hazards can create or worsen leaks under generated pressure or voltage.
|
||||
- Generated hazards can restrict movement through `Unsafe` rules.
|
||||
- Generated sprinkler pressure debt can affect consumer or reactor service.
|
||||
- [ ] Add solver correctness tests.
|
||||
- Known tiny levels report expected win, lose, and inconclusive counts.
|
||||
- Depth-limited branches become inconclusive.
|
||||
- No-choice non-ready states count as losing dead ends.
|
||||
- Canonical action ordering produces stable diagnostics.
|
||||
- Parallel and single-threaded solver runs produce identical reports.
|
||||
- [ ] Add solver performance tests.
|
||||
- Branch-heavy generated samples finish within the configured timeout.
|
||||
- Representative solving does not retain unbounded traces.
|
||||
- Clone or pooling behavior does not leak mutable state across branches.
|
||||
- [ ] Add Godot integration coverage where practical.
|
||||
- Generated JSON loads through `LevelStateLoader`.
|
||||
- Generated JSON saves through the existing level save path.
|
||||
- `FrontendSession` can point random mode at generated JSON.
|
||||
|
||||
## P2 Documentation And Tooling
|
||||
|
||||
- [ ] Update `docs/design.md`.
|
||||
- Document procedural generation inputs.
|
||||
- Document solver outcomes and metrics.
|
||||
- Document generated structural-integrity and pressure-hazard expectations.
|
||||
- Document random play seed behavior.
|
||||
- [ ] Update `docs/UX.md`.
|
||||
- Replace placeholder random-level wording.
|
||||
- Describe generation controls, authoring controls, metrics display, progress, cancellation, and failure states.
|
||||
- Describe generated JSON editing and save-back flow.
|
||||
- [ ] Add authoring guidance.
|
||||
- Explain how generated levels can be promoted into campaign candidates.
|
||||
- Explain how increasing choices and lose/win ratio supports campaign progression.
|
||||
- Explain recommended solver depth and target complexity ranges.
|
||||
- [ ] Keep task status current.
|
||||
- Check off items as implementation lands.
|
||||
- Remove outdated task text instead of adding past-tense prose.
|
||||
- Keep verification commands aligned with repo instructions.
|
||||
|
||||
## Verification Rules
|
||||
|
||||
- [ ] Run focused solver and generator tests after solver or generation changes.
|
||||
- [ ] Run full `dotnet test` before committing simulation, serializer, or generated-data changes.
|
||||
- [ ] Run `dotnet build ReactorMaintenance.slnx --no-restore` and `dotnet test ReactorMaintenance.slnx --no-restore` sequentially.
|
||||
- [ ] For code changes on Windows, run `python D:\Code\crlf.py` for recognized touched files.
|
||||
- [ ] For code changes on Windows, run `jb cleanupcode --verbosity:ERROR ReactorMaintenance.slnx`.
|
||||
- [ ] For documentation-only iterations, no cleanup pass is required.
|
||||
- [ ] Commit each completed iteration with a brief summary.
|
||||
|
||||
128
docs/ART.md
Normal file
@@ -0,0 +1,128 @@
|
||||
# Reactor Maintenance Art Bible
|
||||
|
||||
This document defines the visual style for generated and hand-authored art. Include the prompt block when generating image assets, then add the asset-specific notes for the object being created.
|
||||
|
||||
## Prompt Block
|
||||
|
||||
Create readable top-down 2D game art for Reactor Maintenance: modern, elegant, stylized, and playful sci-fi with a scrappy industrial reactor-maintenance tone. Use crisp simplified shapes, functional machinery with charm, selective dark outlines, restrained lighting, and cool steel or graphite base materials with clear semantic accent colors. Preserve readability at small tile and icon sizes. Use sparse material texture based on hue variation, panel color shifts, subtle oxidation, heat tinting, and worn paint color changes instead of noisy luminance contrast. Avoid photorealism, excessive grime, tiny mechanical clutter, heavy glow, CRT effects, cyberpunk neon overload, and high-frequency scratches or speckles.
|
||||
|
||||
## Core Style
|
||||
|
||||
- Perspective: readable top-down tactical game view.
|
||||
- Rendering: crisp stylized 2D, flat-to-soft shading, clean silhouettes, minimal atmospheric effects.
|
||||
- Mood: scrappy industrial sci-fi, tense but approachable, not sterile and not horror.
|
||||
- Shape language: chunky readable machinery, rounded bevels, practical handles, thick pipes, oversized state lights, and mild asymmetry.
|
||||
- Edge treatment: selective dark outlines around gameplay-critical silhouettes, openings, and layered components. Do not outline every tiny surface detail.
|
||||
- Detail density: large forms first, medium details second, small details only as accents.
|
||||
|
||||
## Color System
|
||||
|
||||
- Base materials: cool graphite, blue-gray steel, muted charcoal, dark desaturated teal.
|
||||
- Neutral contrast: keep major surfaces in grouped value bands so details do not sparkle or flicker when downscaled.
|
||||
- Fuel: saturated red accents and warning stains.
|
||||
- Coolant: cyan or blue accents, slightly clean and cold.
|
||||
- Electricity: yellow accents, sharp and high-energy.
|
||||
- Heat: orange to white-hot accents, used sparingly.
|
||||
- Ready or won: bright green-white accents.
|
||||
- Lost or critical: red with limited orange heat support.
|
||||
|
||||
Use accent colors as gameplay information, not decoration. Do not let background panels compete with hazards, robot position, or reactor state.
|
||||
|
||||
## Texture Rules
|
||||
|
||||
- Prefer hue variation over luminance variation.
|
||||
- Show wear through shifted paint colors, oxidized edges, faint coolant discoloration, heat-blued metal, and mismatched replacement panels.
|
||||
- Keep texture marks broad enough to read at 64px and 32px.
|
||||
- Use 1-3 controlled texture zones per asset, not all-over noise.
|
||||
- Avoid fine scratches, random speckles, dense grime, noisy rust, photographic stains, and texture that changes the silhouette.
|
||||
- If an asset must look dirty, make the dirt a clear shape with a distinct hue, not a field of dark dots.
|
||||
|
||||
## Downscale Readability
|
||||
|
||||
- Design every prop to remain recognizable at tile size before adding surface detail.
|
||||
- Use 2-3 dominant shape groups per asset.
|
||||
- Keep the outer silhouette simple and distinctive.
|
||||
- Reserve the highest contrast for the object boundary, key interaction point, and state indicator.
|
||||
- Leave quiet space inside each asset so icons, badges, and selection highlights can sit on top.
|
||||
- Test mentally at 128px, 64px, and 32px. If the asset becomes a noisy blob, remove detail before adding more contrast.
|
||||
|
||||
## Asset Guidance
|
||||
|
||||
### Floors And Walls
|
||||
|
||||
Floors should be quiet, matte, and grid-readable: graphite plates, cool steel panels, rubberized seams, or worn maintenance decking. Use subtle hue shifts between plates instead of strong light/dark checkerboarding.
|
||||
|
||||
Walls should be heavier than floors with strong silhouettes and clear blocking. They may use darker steel, reinforced ribs, conduit hints, and warning trim, but must not look like interactable props.
|
||||
|
||||
### Pipes And Underground Networks
|
||||
|
||||
Pipes should be thick, continuous, and readable as routes. Use carrier color accents along otherwise muted metal forms:
|
||||
|
||||
- fuel pipes: red bands, valve marks, or inner glow kept low,
|
||||
- coolant pipes: cyan-blue bands, cold tinting, condensation shapes,
|
||||
- electricity conduits: yellow hazard marks, angular housings, insulated cable cues.
|
||||
|
||||
Avoid tiny pipe bundles. Junctions, sources, leaks, and breaks need larger silhouettes than ordinary pipe segments.
|
||||
|
||||
### Props
|
||||
|
||||
Props should look functional and slightly overbuilt. Each prop needs one obvious interaction feature: lever, screen, port, valve, hatch, supply cap, or activation core. State lights should be large enough to read at icon size.
|
||||
|
||||
Common prop rules:
|
||||
|
||||
- control terminals: screen-first silhouette, teal or green display, chunky base,
|
||||
- diagnostic terminals: stronger sensor or scanner identity, purple-blue secondary accents allowed,
|
||||
- cooling pumps: round tank or impeller identity, coolant accent,
|
||||
- generators: coil, flywheel, or battery silhouette, electricity accent,
|
||||
- pressure regulators: gauge and valve identity, simple face markings,
|
||||
- reactor controls: largest authority object, central core or keyed activation surface.
|
||||
|
||||
### Robot
|
||||
|
||||
The robot should be compact, practical, and likable without becoming comedic. Use a strong top-down body shape, small tool arms or maintenance appendages, a visible direction cue, and one clear status light. Keep the robot brighter than floor tiles but less saturated than hazards.
|
||||
|
||||
### Hazards And Failures
|
||||
|
||||
Hazards must be immediately distinct from props and terrain:
|
||||
|
||||
- fuel: red slicks with smooth pooling shapes,
|
||||
- coolant: blue-cyan spill shapes with colder edges,
|
||||
- electricity: angular yellow arcs or charged patches,
|
||||
- heat/fire: orange-white cores with simple flame or shimmer shapes.
|
||||
|
||||
Failure images may be more dramatic than gameplay tiles, but must keep a single readable focal point and avoid painterly chaos. Use large hazard shapes, clear silhouettes, and limited supporting debris.
|
||||
|
||||
### UI Icons And Badges
|
||||
|
||||
UI art should be flatter and cleaner than world props. Use the same semantic colors, simple pictograms, and selective outlines. Icons must remain readable at 24px. Avoid mini illustrations inside UI controls.
|
||||
|
||||
## Composition For Generated Images
|
||||
|
||||
- Use transparent or simple neutral backgrounds for props, icons, tiles, and pipe pieces.
|
||||
- Center the asset with enough padding for selection outlines and Godot import cropping.
|
||||
- Avoid dramatic camera angles for gameplay assets.
|
||||
- Do not add labels, text, watermarks, borders, frames, or UI chrome unless the prompt asks for a UI asset.
|
||||
- For tilemaps, keep lighting direction consistent and avoid shadows that imply non-grid depth.
|
||||
|
||||
## Negative Prompt
|
||||
|
||||
no photorealism, no noisy texture, no high-frequency detail, no dense grime, no random speckles, no tiny wires everywhere, no excessive scratches, no low-contrast muddy silhouette, no unreadable clutter, no dramatic perspective, no generic cyberpunk city neon, no heavy bloom, no CRT scanlines, no glitch effect, no horror gore, no text, no watermark
|
||||
|
||||
## Existing Asset Note
|
||||
|
||||
The current Win2D PNGs are useful subject references for the required asset categories, but they are not the target style. Future assets should be simpler, cleaner, more silhouette-driven, and more readable when scaled down.
|
||||
|
||||
## Generated Godot Cutouts
|
||||
|
||||
The first transparent Godot cutouts live under `src/ReactorMaintenance.Godot/Assets`. They were generated with `gpt-image-1.5` using the mood sheets in `output/imagegen/godot-mood` as references:
|
||||
|
||||
- `Assets/Characters/maintenance_robot.png`
|
||||
- `Assets/Ui/primary_button_accent.png`
|
||||
- `Assets/Ui/state_badge_frame.png`
|
||||
- `Assets/Ui/fuel_icon.png`
|
||||
- `Assets/Ui/coolant_icon.png`
|
||||
- `Assets/Ui/electric_icon.png`
|
||||
- `Assets/Ui/heat_shield_icon.png`
|
||||
- `Assets/Ui/scanner_eye_icon.png`
|
||||
|
||||
The exact generation prompts are recorded in `src/ReactorMaintenance.Godot/Assets/generated_cutouts_prompts.jsonl`.
|
||||
660
docs/CAMPAIGN.md
Normal file
@@ -0,0 +1,660 @@
|
||||
# Reactor Maintenance Campaign Design
|
||||
|
||||
This document prepares the handcrafted campaign for implementation. It references `docs/design.md` terminology directly; campaign text should not introduce mechanics that are absent from the design document.
|
||||
|
||||
## Campaign Goals
|
||||
|
||||
- Teach one `LengthyAction` grammar at a time, then combine systems.
|
||||
- Keep `MoveRobot`, inspection, and visible-state reading calm and free.
|
||||
- Make every environment-changing action trigger exactly one `Pulse`.
|
||||
- Keep `Forecast` output systemic and available only at an active and powered `AllSeeingEyeTerminal`.
|
||||
- Avoid treating missing `ReactorReadiness` as a failure state; it only blocks `Ready`.
|
||||
- Use `UnsafeEntryLoss` only when `MoveRobot` enters an `Unsafe` destination cell.
|
||||
- Keep the campaign small enough for a two-week game jam.
|
||||
|
||||
## Campaign Structure
|
||||
|
||||
The campaign uses one tutorial plus six groups. Group 1 intentionally has two levels; Groups 2-6 have three levels each.
|
||||
|
||||
| Group | Levels | Networks | Main Lesson |
|
||||
| ----- | ------ | -------- | ----------- |
|
||||
| Tutorial | 1 | `Fuel` | `LengthyAction` triggers `Pulse`; `Ready` then `ActivateReactor` wins. |
|
||||
| 1 | 2 | `Fuel` | `FlowProp`, `IsolationValveProp`, reachable leaks, and direct `ReactorReadiness`. |
|
||||
| 2 | 3 | `Coolant` | `ConsumerProp`, `Producing`, `SprinklerControlProp`, wall-mounted `CoolantSprinklerValve`, `SprinklerWater`, pressure recovery. |
|
||||
| 3 | 3 | `Electricity` | wall `Electricity` leaks, repair faces, powered `DoorProp`, and powered `AllSeeingEyeTerminal`. |
|
||||
| 4 | 3 | `Fuel` + `Electricity` | first dual-system interaction: `Ignite` creates recurring `Heat`. |
|
||||
| 5 | 3 | `Coolant` + `Electricity` | `SprinklerWater`, `Evaporation`, and wet-electricity `Conduct` risk. |
|
||||
| 6 | 3 | `Fuel` + `Coolant` + `Electricity` | full startup with fire, suppression, wet conduction, and terminal-local `Forecast` use. |
|
||||
|
||||
## Authoring Rules
|
||||
|
||||
Before `AllSeeingEyeTerminal` appears in 3-2, all levels must be readable from surface props and visible effects alone. Use simple branches, clear source positions, visible leak access, and obvious valve placement.
|
||||
|
||||
Every non-tutorial level must offer at least two plausible first `LengthyAction` choices. The later the group, the more those choices should require sequencing rather than a single obvious answer.
|
||||
|
||||
Do not author level-specific forecast strings. The campaign can require that systemic `Forecast` output is useful, but the level spec must describe the underlying state that fixed forecast rules can detect.
|
||||
|
||||
Do not describe numeric surface thresholds or campaign-specific formulas. Use `Unsafe` only as the formal movement consequence defined in `docs/design.md`.
|
||||
|
||||
Each level below includes:
|
||||
|
||||
- `Purpose`: the reason the level exists.
|
||||
- `Setup`: authored starting conditions.
|
||||
- `Timeline`: intended reasoning and `Pulse` consequences.
|
||||
- `Win`: required `ReactorReadiness` and `ActivateReactor` condition.
|
||||
- `Lose`: terminal failure or `UnsafeEntryLoss` conditions.
|
||||
- `ImplementationNotes`: content constraints for level data.
|
||||
|
||||
## Tutorial: First Pulse
|
||||
|
||||
### T0 - Wake The Feed
|
||||
|
||||
Purpose: Teach that one accepted `LengthyAction` triggers one `Pulse`, after which `Ready` can be activated.
|
||||
|
||||
Setup:
|
||||
|
||||
- Networks: `Fuel`.
|
||||
- One disabled `Fuel` `FlowProp` feeds the `ReactorControlProp`.
|
||||
- Required consumer counts are zero.
|
||||
- No leaks, no surface hazards, no `DoorProp`, no `AllSeeingEyeTerminal`.
|
||||
- Robot starts a few floor cells away from the `FlowProp`.
|
||||
|
||||
Timeline:
|
||||
|
||||
1. Player uses `MoveRobot` and inspection freely.
|
||||
2. Player uses `InteractProp` on the `Fuel` `FlowProp`.
|
||||
3. One `Pulse` propagates `Fuel` to the `ReactorControlProp`.
|
||||
4. Level enters `Ready`.
|
||||
5. Player uses `ActivateReactor`.
|
||||
|
||||
Win: `ReactorControlProp` has positive `Fuel` amount and pressure, required consumer counts are zero, and `ActivateReactor` changes level state to `Won`.
|
||||
|
||||
Lose: No designed loss condition beyond invalid actions; this is guided tutorial content.
|
||||
|
||||
ImplementationNotes:
|
||||
|
||||
- This is the only single-action setup.
|
||||
- Do not include `Forecast`; no `AllSeeingEyeTerminal` exists.
|
||||
|
||||
## Group 1: `Fuel`
|
||||
|
||||
Group 1 teaches pressure-fed `Fuel` faults with no required `ConsumerProp` instances and no `Forecast`. Branches must be visually obvious from `FlowProp`, `IsolationValveProp`, leak access, and `ReactorControlProp` placement.
|
||||
|
||||
### 1-1 - Bleed Line
|
||||
|
||||
Purpose: Teach that source startup, reactor feed, and leak isolation are separate decisions.
|
||||
|
||||
Setup:
|
||||
|
||||
- Networks: `Fuel`.
|
||||
- One disabled `Fuel` `FlowProp`.
|
||||
- One `ReactorControlProp` on a main branch that starts blocked by a closed `IsolationValveProp`.
|
||||
- One leaking `Fuel` segment on a short side branch with a reachable floor access cell.
|
||||
- One open `IsolationValveProp` on the side branch before the leak.
|
||||
- Required consumer counts are zero.
|
||||
|
||||
Timeline:
|
||||
|
||||
1. First choice A: enable the `FlowProp`; `Pulse` feeds the open leak branch and creates visible `LeakedFuel`, but the closed reactor-feed valve keeps `ReactorReadiness` missing.
|
||||
2. First choice B: open the reactor-feed `IsolationValveProp`; `Pulse` changes the route but does not create `ReactorReadiness` because the source is still disabled.
|
||||
3. First choice C: close the leak-branch `IsolationValveProp`; `Pulse` prevents fresh leak injection but leaves the reactor unfed.
|
||||
4. Player isolates or repairs the leak, enables the source, and opens the reactor-feed branch.
|
||||
5. Player uses `ActivateReactor`.
|
||||
|
||||
Win: `ReactorReadiness` is true for `Fuel` with zero required consumers, then `ActivateReactor`.
|
||||
|
||||
Lose: `Heat` terminal failure is not present; `LeakedFuel` alone does not cause `UnsafeEntryLoss`.
|
||||
|
||||
ImplementationNotes:
|
||||
|
||||
- The leak branch must be visually identifiable without underground visibility.
|
||||
- No first `LengthyAction` should make the level `Ready`.
|
||||
|
||||
### 1-2 - Valve Choice
|
||||
|
||||
Purpose: Teach branch-specific isolation versus over-isolating the main feed.
|
||||
|
||||
Setup:
|
||||
|
||||
- Networks: `Fuel`.
|
||||
- One enabled `Fuel` `FlowProp` feeds a fork.
|
||||
- One fork branch reaches the `ReactorControlProp` through a closed reactor-feed `IsolationValveProp`.
|
||||
- The other fork branch reaches a leaking `Fuel` segment.
|
||||
- One main `IsolationValveProp` before the fork.
|
||||
- One leak-branch `IsolationValveProp` before the leaking segment.
|
||||
- Required consumer counts are zero.
|
||||
|
||||
Timeline:
|
||||
|
||||
1. First choice A: close the main `IsolationValveProp`; `Pulse` stops fresh leak injection and also prevents future reactor feed until reopened.
|
||||
2. First choice B: close the leak-branch `IsolationValveProp`; `Pulse` stops fresh leak injection while preserving the main route.
|
||||
3. First choice C: repair the leak; `Pulse` stops future leak injection but leaves the reactor-feed valve still closed.
|
||||
4. Player opens the reactor-feed branch and keeps or restores the main feed.
|
||||
5. Player uses `ActivateReactor`.
|
||||
|
||||
Win: `ReactorReadiness` is true for `Fuel` with zero required consumers, then `ActivateReactor`.
|
||||
|
||||
Lose: No designed `UnsafeEntryLoss` from `LeakedFuel` alone; terminal failure only if fixed rules mark the level `Lost`.
|
||||
|
||||
ImplementationNotes:
|
||||
|
||||
- Use surface labels or prop placement to distinguish main valve and leak-branch valve.
|
||||
- The level must not begin `Ready`; the reactor-feed branch starts closed.
|
||||
|
||||
## Group 2: `Coolant`
|
||||
|
||||
Group 2 introduces `ConsumerProp` requirements and `CoolantSprinklerValve` pressure tradeoffs. Since `Coolant` alone is not a fire system yet, these levels focus on `Producing` consumers, `SprinklerControlProp`, `SprinklerWater`, `Evaporation`, and pressure recovery.
|
||||
|
||||
### 2-1 - Prime The Pump
|
||||
|
||||
Purpose: Introduce `ConsumerProp` as a required production checklist.
|
||||
|
||||
Setup:
|
||||
|
||||
- Networks: `Coolant`.
|
||||
- One enabled `Coolant` `FlowProp`.
|
||||
- Two visible `Coolant` `ConsumerProp` instances:
|
||||
- one starts `Enabled` but is behind a closed branch `IsolationValveProp`, so it is not `Producing`,
|
||||
- one starts `Disabled` on a supplied branch, so it is supplied but not `Producing`.
|
||||
- One `ReactorControlProp`.
|
||||
- Required consumers: two `Coolant`.
|
||||
- The `ReactorControlProp` is fed from the main branch.
|
||||
|
||||
Timeline:
|
||||
|
||||
1. First choice A: enable the supplied but disabled `ConsumerProp`; `Pulse` makes only that consumer `Producing`.
|
||||
2. First choice B: open the blocked branch `IsolationValveProp`; `Pulse` supplies the already enabled consumer.
|
||||
3. First choice C: close the main branch; `Pulse` demonstrates that consumer production depends on supply and delays readiness.
|
||||
4. Player sets both consumer branches so both `Coolant` `ConsumerProp` instances are `Enabled` and `Producing`.
|
||||
5. Player uses `ActivateReactor`.
|
||||
|
||||
Win: `ReactorControlProp` has positive `Coolant` amount and pressure, two required `Coolant` `ConsumerProp` instances are `Enabled` and `Producing`, then `ActivateReactor`.
|
||||
|
||||
Lose: No designed terminal pressure; only invalid actions or explicit terminal rules.
|
||||
|
||||
ImplementationNotes:
|
||||
|
||||
- This level introduces the difference between `Enabled`, `Supplied`, and `Producing` before any `CoolantSprinklerValve`.
|
||||
- Use visible prop placement; no `AllSeeingEyeTerminal`.
|
||||
|
||||
### 2-2 - Sprinkler Debt
|
||||
|
||||
Purpose: Teach that an enabled wall-mounted `CoolantSprinklerValve` can reduce pressure until disabled or isolated.
|
||||
|
||||
Setup:
|
||||
|
||||
- Networks: `Coolant`.
|
||||
- One enabled `Coolant` `FlowProp`.
|
||||
- One wall-mounted `CoolantSprinklerValve` starts discharging because its linked `SprinklerControlProp` starts `Enabled`.
|
||||
- One nearby `SprinklerControlProp` is the only direct sprinkler interaction.
|
||||
- The sprinkler has one adjacent floor outlet/access face and emits `SprinklerWater`.
|
||||
- One `Coolant` `ConsumerProp` and the `ReactorControlProp` are on the same pressure-sensitive branch.
|
||||
- Required consumers: one `Coolant`.
|
||||
- One upstream `IsolationValveProp` can shut the sprinkler branch without shutting the consumer branch.
|
||||
|
||||
Timeline:
|
||||
|
||||
1. First choice A: disable the linked `SprinklerControlProp`; `Pulse` stops discharge and allows pressure recovery.
|
||||
2. First choice B: close the sprinkler-branch `IsolationValveProp`; `Pulse` stops discharge by cutting supply to the sprinkler.
|
||||
3. First choice C: enable or adjust the required `ConsumerProp` first; `Pulse` keeps sprinkler debt visible.
|
||||
4. `SprinklerWater` evaporates through normal `Step` rules during pulses caused by useful actions.
|
||||
5. Player reaches `ReactorReadiness` after `Coolant` pressure and required consumer production recover.
|
||||
6. Player uses `ActivateReactor`.
|
||||
|
||||
Win: `ReactorControlProp` has positive `Coolant` amount and pressure, one `Coolant` `ConsumerProp` is `Enabled` and `Producing`, then `ActivateReactor`.
|
||||
|
||||
Lose: No authored `Heat` or fire; avoid loss pressure except explicit terminal rules.
|
||||
|
||||
ImplementationNotes:
|
||||
|
||||
- Do not author standalone `Heat` in this `Coolant`-only level.
|
||||
- Sprinkler is wall-mounted with exactly one outlet/access floor face and exactly one linked `SprinklerControlProp`.
|
||||
- This level visually teaches `SprinklerWater` and `Evaporation` without suppression yet.
|
||||
|
||||
### 2-3 - Split Flow
|
||||
|
||||
Purpose: Teach `JunctionProp` routing and consumer requirements in a readable `Coolant` network.
|
||||
|
||||
Setup:
|
||||
|
||||
- Networks: `Coolant`.
|
||||
- One enabled `Coolant` `FlowProp`.
|
||||
- One `JunctionProp` controls two consumer branches.
|
||||
- Three visible `Coolant` `ConsumerProp` instances.
|
||||
- One wall-mounted `CoolantSprinklerValve` starts `Disabled` because its linked `SprinklerControlProp` starts `Disabled`.
|
||||
- Required consumers: two `Coolant`.
|
||||
- One branch has a weakened or leaking `Coolant` segment that emits `SprinklerWater` when fed.
|
||||
|
||||
Timeline:
|
||||
|
||||
1. First choice A: cycle the `JunctionProp`; `Pulse` changes which consumers can become `Producing`.
|
||||
2. First choice B: close the damaged branch `IsolationValveProp`; `Pulse` stops fresh `SprinklerWater` injection but reduces available production paths.
|
||||
3. First choice C: repair the `Coolant` leak; `Pulse` stops future leak injection but does not remove existing `SprinklerWater`.
|
||||
4. Player configures routing so two `Coolant` consumers are `Enabled` and `Producing`.
|
||||
5. Player uses `ActivateReactor`.
|
||||
|
||||
Win: `ReactorControlProp` has positive `Coolant` amount and pressure, two `Coolant` `ConsumerProp` instances are `Enabled` and `Producing`, then `ActivateReactor`.
|
||||
|
||||
Lose: No designed `UnsafeEntryLoss` from `SprinklerWater` alone; otherwise no designed terminal pressure.
|
||||
|
||||
ImplementationNotes:
|
||||
|
||||
- Keep all relevant branches readable from surface prop placement.
|
||||
- No `Forecast`; no `AllSeeingEyeTerminal`.
|
||||
|
||||
## Group 3: `Electricity`
|
||||
|
||||
Group 3 introduces wall `Electricity` leaks, powered `DoorProp` behavior, and then powered `AllSeeingEyeTerminal`. Level 3-1 must remain readable without underground visibility.
|
||||
|
||||
### 3-1 - Door Circuit
|
||||
|
||||
Purpose: Introduce `DoorProp` power requirements while reinforcing wall `Electricity` leak handling.
|
||||
|
||||
Setup:
|
||||
|
||||
- Networks: `Electricity`.
|
||||
- One enabled `Electricity` `FlowProp`.
|
||||
- One `DoorProp` begins `Closed` and its local supply branch starts blocked by an `IsolationValveProp`.
|
||||
- One wall-based `Electricity` leak with exactly one adjacent floor emission/repair face.
|
||||
- One `Electricity` `ConsumerProp` starts `Disabled` on the blocked door-supply branch.
|
||||
- One `ReactorControlProp` is fed by the blocked door-supply branch.
|
||||
- Required consumers: one `Electricity`.
|
||||
- One `IsolationValveProp` can cut voltage to the leaking wall segment.
|
||||
|
||||
Timeline:
|
||||
|
||||
1. First choice A: interact with the unpowered `DoorProp`; `Pulse` resolves, the door stays `Closed`, and the leak can continue.
|
||||
2. First choice B: open the door-supply `IsolationValveProp`; `Pulse` powers the door and keeps the leak state readable.
|
||||
3. First choice C: isolate or repair the wall leak first; `Pulse` reduces `LeakedElectricity` risk before door operation.
|
||||
4. Player powers and toggles the `DoorProp`, then restores consumer production and reactor feed.
|
||||
5. Player uses `ActivateReactor`.
|
||||
|
||||
Win: `ReactorControlProp` has positive `Electricity` amount and voltage, one `Electricity` `ConsumerProp` is `Enabled` and `Producing`, then `ActivateReactor`.
|
||||
|
||||
Lose: `UnsafeEntryLoss` from moving into `Unsafe` `LeakedElectricity`; terminal failure if fixed rules mark `Lost`.
|
||||
|
||||
ImplementationNotes:
|
||||
|
||||
- Emission/repair face must be visually unambiguous.
|
||||
- No `Forecast`; no `AllSeeingEyeTerminal`.
|
||||
- Door supply and leak isolation must be readable from nearby props.
|
||||
|
||||
### 3-2 - The First Eye
|
||||
|
||||
Purpose: Introduce powered `AllSeeingEyeTerminal` access and terminal-local `Forecast`.
|
||||
|
||||
Setup:
|
||||
|
||||
- Networks: `Electricity`.
|
||||
- One `AllSeeingEyeTerminal` in a side room.
|
||||
- The terminal's local supply branch starts blocked by an `IsolationValveProp`.
|
||||
- One `Electricity` `ConsumerProp` starts `Disabled` on a clearly separate supplied branch.
|
||||
- One `ReactorControlProp` starts blocked by a closed reactor-feed `IsolationValveProp`.
|
||||
- Required consumers: one `Electricity`.
|
||||
- One simple wall `Electricity` leak is visible from the surface.
|
||||
- One optional `DoorProp` is present only if its powered state remains obvious without terminal visibility.
|
||||
|
||||
Timeline:
|
||||
|
||||
1. First choice A: interact with the unpowered `AllSeeingEyeTerminal`; `Pulse` resolves, but no underground view or `Forecast` appears.
|
||||
2. First choice B: power the terminal branch; `Pulse` makes later terminal use effective.
|
||||
3. First choice C: isolate or repair the visible leak first; `Pulse` reduces visible risk but delays terminal information.
|
||||
4. Player activates the powered terminal, uses systemic `Forecast`, restores required production and control feed.
|
||||
5. Player uses `ActivateReactor`.
|
||||
|
||||
Win: `ReactorControlProp` has positive `Electricity` amount and voltage, one `Electricity` `ConsumerProp` is `Enabled` and `Producing`, then `ActivateReactor`.
|
||||
|
||||
Lose: `UnsafeEntryLoss` from entering `Unsafe` `LeakedElectricity`; terminal failure if fixed rules mark `Lost`.
|
||||
|
||||
ImplementationNotes:
|
||||
|
||||
- This is the first campaign level with `Forecast`.
|
||||
- `Forecast` output must be systemic; do not author level text.
|
||||
- The level remains solvable from visible information, but powering the terminal should clearly improve confidence.
|
||||
|
||||
### 3-3 - Blind Grid
|
||||
|
||||
Purpose: Require powered `AllSeeingEyeTerminal` use in a complex electricity routing puzzle.
|
||||
|
||||
Setup:
|
||||
|
||||
- Networks: `Electricity`.
|
||||
- One `AllSeeingEyeTerminal` starts unpowered on a branch that can be energized early.
|
||||
- Two required `Electricity` `ConsumerProp` instances sit on different branches.
|
||||
- One `DoorProp` contains surface propagation along a useful route and requires local power.
|
||||
- One visible wall `Electricity` leak and one weakened high-voltage segment are on different possible routes.
|
||||
- Required consumers: two `Electricity`.
|
||||
|
||||
Timeline:
|
||||
|
||||
1. First choice A: power the terminal branch; `Pulse` enables a later useful terminal interaction while other risks advance.
|
||||
2. First choice B: interact with the unpowered terminal first; `Pulse` changes nothing and demonstrates the cost of missing power.
|
||||
3. First choice C: route power toward visible consumers without terminal information; `Pulse` may feed one consumer while worsening the hidden weak route.
|
||||
4. Player activates the powered terminal, reads underground topology and systemic `Forecast`, then chooses which branches to isolate, repair, or power.
|
||||
5. Player produces both required consumers and powers the `ReactorControlProp`.
|
||||
6. Player uses `ActivateReactor`.
|
||||
|
||||
Win: `ReactorControlProp` has positive `Electricity` amount and voltage, two `Electricity` `ConsumerProp` instances are `Enabled` and `Producing`, then `ActivateReactor`.
|
||||
|
||||
Lose: `UnsafeEntryLoss` from `Unsafe` `LeakedElectricity`, terminal `Heat` if generated by fixed rules, or another fixed terminal rule.
|
||||
|
||||
ImplementationNotes:
|
||||
|
||||
- This is the first level where terminal use is essential rather than optional.
|
||||
- The visible-only read should be plausible but incomplete.
|
||||
|
||||
## Group 4: `Fuel` + `Electricity`
|
||||
|
||||
Group 4 introduces the first dual-system interaction: `Fuel` and `Electricity` can `Ignite`, generating recurring `Heat`. From this point onward, `Heat` is a recurring campaign problem.
|
||||
|
||||
### 4-1 - First Spark
|
||||
|
||||
Purpose: Teach that combining `LeakedFuel` and `LeakedElectricity` can create `Heat`.
|
||||
|
||||
Setup:
|
||||
|
||||
- Networks: `Fuel`, `Electricity`.
|
||||
- One visible `Fuel` leak emits near a corridor.
|
||||
- One wall `Electricity` leak emits toward the same corridor.
|
||||
- One `Fuel` `ConsumerProp`, one `Electricity` `ConsumerProp`, and one `ReactorControlProp` on both networks.
|
||||
- Required consumers: one `Fuel`, one `Electricity`.
|
||||
- One `IsolationValveProp` can isolate either damaged branch.
|
||||
|
||||
Timeline:
|
||||
|
||||
1. First choice A: enable `Fuel`; `Pulse` grows `LeakedFuel`.
|
||||
2. First choice B: enable `Electricity`; `Pulse` grows `LeakedElectricity`.
|
||||
3. First choice C: isolate or repair one leak before both networks are active.
|
||||
4. If both leaked values meet, fixed surface rules can `Ignite` and create `Heat`.
|
||||
5. Player stabilizes at least one leak path, restores required production, and activates.
|
||||
|
||||
Win: `ReactorReadiness` is true for `Fuel` and `Electricity`, required consumers are `Enabled` and `Producing`, then `ActivateReactor`.
|
||||
|
||||
Lose: terminal `Heat`, `UnsafeEntryLoss` from `Unsafe` `Heat`, `LeakedElectricity`, or wet-electric cells, or fixed terminal failure.
|
||||
|
||||
ImplementationNotes:
|
||||
|
||||
- Use only designed interactions from the hazard pair table.
|
||||
- `Heat` appears as a consequence of `Ignite`, not as an arbitrary authored patch.
|
||||
|
||||
### 4-2 - Break Before Make
|
||||
|
||||
Purpose: Teach source sequencing: fixing or isolating first can be safer than powering both networks.
|
||||
|
||||
Setup:
|
||||
|
||||
- Networks: `Fuel`, `Electricity`.
|
||||
- `Fuel` source starts enabled and a small visible `LeakedFuel` patch exists.
|
||||
- `Electricity` source starts disabled.
|
||||
- One wall `Electricity` leak is near the fuel patch.
|
||||
- One powered `DoorProp` can contain surface spread if operated before electricity/fuel overlap grows.
|
||||
- Required consumers: one `Fuel`, one `Electricity`.
|
||||
|
||||
Timeline:
|
||||
|
||||
1. First choice A: enable `Electricity`; `Pulse` may create `Heat` if leaked values interact.
|
||||
2. First choice B: close or open a powered `DoorProp` to change surface propagation before energizing.
|
||||
3. First choice C: isolate or repair the `Fuel` leak first.
|
||||
4. Player prevents or contains `Ignite`, then restores both required services.
|
||||
5. Player uses `ActivateReactor`.
|
||||
|
||||
Win: `ReactorReadiness` is true for `Fuel` and `Electricity`, required consumers are `Enabled` and `Producing`, then `ActivateReactor`.
|
||||
|
||||
Lose: terminal `Heat` or `UnsafeEntryLoss` from `Unsafe` cells.
|
||||
|
||||
ImplementationNotes:
|
||||
|
||||
- Door mechanics are centered on electricity supply: the `DoorProp` can only toggle while powered.
|
||||
- If an `AllSeeingEyeTerminal` is present, its `Forecast` is optional and terminal-local.
|
||||
|
||||
### 4-3 - Hot Bypass
|
||||
|
||||
Purpose: Combine structural integrity, route choice, and fire risk.
|
||||
|
||||
Setup:
|
||||
|
||||
- Networks: `Fuel`, `Electricity`.
|
||||
- Two visible branches can satisfy each required consumer set.
|
||||
- One high-voltage branch threatens structural degradation near a `Fuel` leak path.
|
||||
- One `DoorProp` can contain `Heat` or `LeakedElectricity` propagation if powered and closed.
|
||||
- Required consumers: one `Fuel`, one `Electricity`.
|
||||
- One `HeatShield` supply is available for movement safety.
|
||||
|
||||
Timeline:
|
||||
|
||||
1. First choice A: route `Electricity` through the high-voltage branch; `Pulse` may create a new wall leak by fixed integrity rules.
|
||||
2. First choice B: isolate the `Fuel` bypass; `Pulse` reduces ignition risk but changes available production path.
|
||||
3. First choice C: pick up `HeatShield`; `Pulse` improves future movement safety but does not solve the system.
|
||||
4. Player chooses one stable production route per network.
|
||||
5. Player uses `ActivateReactor`.
|
||||
|
||||
Win: `ReactorReadiness` is true for `Fuel` and `Electricity`, required consumers are `Enabled` and `Producing`, then `ActivateReactor`.
|
||||
|
||||
Lose: terminal `Heat`, `UnsafeEntryLoss` from `Unsafe` cells, or fixed terminal failure.
|
||||
|
||||
ImplementationNotes:
|
||||
|
||||
- `HeatShield` only affects movement safety; it does not quench `Heat`.
|
||||
- Avoid perfect cleanup requirements.
|
||||
|
||||
## Group 5: `Coolant` + `Electricity`
|
||||
|
||||
Group 5 teaches that `SprinklerWater` is useful system material but dangerous with `Electricity`. `Evaporation` becomes important without adding a wait command.
|
||||
|
||||
### 5-1 - Charged Water
|
||||
|
||||
Purpose: Teach wet-electricity `Conduct`.
|
||||
|
||||
Setup:
|
||||
|
||||
- Networks: `Coolant`, `Electricity`.
|
||||
- One wall-mounted `CoolantSprinklerValve` starts discharging because its linked `SprinklerControlProp` starts `Enabled`.
|
||||
- Initial `SprinklerWater` is visible in a corridor between the sprinkler outlet and an electricity emission face.
|
||||
- One wall `Electricity` leak emits toward that corridor.
|
||||
- Required consumers: one `Coolant`, one `Electricity`.
|
||||
- One `IsolationValveProp` can cut voltage to the leak branch.
|
||||
|
||||
Timeline:
|
||||
|
||||
1. First choice A: disable the linked `SprinklerControlProp`; `Pulse` stops new `SprinklerWater` and allows pressure recovery.
|
||||
2. First choice B: isolate or repair the `Electricity` leak first; `Pulse` reduces wet-conduction risk before drying.
|
||||
3. First choice C: energize electricity production first; `Pulse` shows why wet cells and `LeakedElectricity` are a bad combination.
|
||||
4. If `LeakedElectricity` reaches wet cells, fixed rules use `Conduct`.
|
||||
5. Player restores both required services and activates.
|
||||
|
||||
Win: `ReactorReadiness` is true for `Coolant` and `Electricity`, required consumers are `Enabled` and `Producing`, then `ActivateReactor`.
|
||||
|
||||
Lose: `UnsafeEntryLoss` from `Unsafe` wet-electric cells or fixed terminal failure.
|
||||
|
||||
ImplementationNotes:
|
||||
|
||||
- No authored forecast prose; terminal-local systemic `Forecast` may warn about wet conduction if a powered terminal exists.
|
||||
- Do not present sprinkler activation as the intended first solution; the problem starts from an already wet corridor.
|
||||
|
||||
### 5-2 - Dry Before Live
|
||||
|
||||
Purpose: Teach that `Evaporation` can be part of useful action sequencing.
|
||||
|
||||
Setup:
|
||||
|
||||
- Networks: `Coolant`, `Electricity`.
|
||||
- Initial `SprinklerWater` exists on a floor route near an `Electricity` leak face.
|
||||
- A wall-mounted `CoolantSprinklerValve` maintains wetness and pressure debt because its linked `SprinklerControlProp` starts `Enabled`.
|
||||
- A powered `DoorProp` can contain electrical surface propagation.
|
||||
- Required consumers: one `Coolant`, one `Electricity`.
|
||||
|
||||
Timeline:
|
||||
|
||||
1. First choice A: disable the linked `SprinklerControlProp`; `Pulse` stops new `SprinklerWater` and allows `Evaporation`.
|
||||
2. First choice B: close an upstream `IsolationValveProp`; `Pulse` cuts sprinkler supply and changes coolant pressure.
|
||||
3. First choice C: power and operate the `DoorProp` before energizing the leak branch.
|
||||
4. Player uses meaningful network actions while `Evaporation` reduces wetness.
|
||||
5. Player energizes safely, restores both services, and activates.
|
||||
|
||||
Win: `ReactorReadiness` is true for `Coolant` and `Electricity`, required consumers are `Enabled` and `Producing`, then `ActivateReactor`.
|
||||
|
||||
Lose: `UnsafeEntryLoss` from `Unsafe` wet-electric cells or fixed terminal failure.
|
||||
|
||||
ImplementationNotes:
|
||||
|
||||
- Do not add a wait or fast-forward action.
|
||||
- Do not author standalone `Heat`; drying comes from `Evaporation` rules during pulses.
|
||||
|
||||
### 5-3 - Eye In The Storm
|
||||
|
||||
Purpose: Combine `AllSeeingEyeTerminal` with wet-conduction routing.
|
||||
|
||||
Setup:
|
||||
|
||||
- Networks: `Coolant`, `Electricity`.
|
||||
- One `AllSeeingEyeTerminal` is reachable but must be powered before activation reveals `Forecast`.
|
||||
- Two wall-mounted `CoolantSprinklerValve` instances affect different outlet faces through linked `SprinklerControlProp` instances.
|
||||
- One `Electricity` route overlaps one wettable corridor underground.
|
||||
- Required consumers: one `Coolant`, one `Electricity`.
|
||||
|
||||
Timeline:
|
||||
|
||||
1. First choice A: power the `AllSeeingEyeTerminal` branch; a later terminal interaction enables terminal-local visibility and systemic `Forecast`.
|
||||
2. First choice B: disable the nearest `SprinklerControlProp` or isolate its coolant supply without terminal information.
|
||||
3. First choice C: isolate electricity before changing sprinkler state.
|
||||
4. Player uses terminal-local information to avoid wet conduction and restore required production.
|
||||
5. Player uses `ActivateReactor`.
|
||||
|
||||
Win: `ReactorReadiness` is true for `Coolant` and `Electricity`, required consumers are `Enabled` and `Producing`, then `ActivateReactor`.
|
||||
|
||||
Lose: `UnsafeEntryLoss` from `Unsafe` wet-electric cells, or fixed terminal failure.
|
||||
|
||||
ImplementationNotes:
|
||||
|
||||
- Terminal should reveal why one sprinkler/electric route combination is riskier.
|
||||
- `Forecast` remains systemic and terminal-local.
|
||||
|
||||
## Group 6: Full Startup
|
||||
|
||||
Group 6 uses `Fuel`, `Coolant`, and `Electricity`. `Heat` can arise from `Fuel` + `Electricity`; `CoolantSprinklerValve` and `SprinklerWater` provide suppression while creating pressure and wet-conduction tradeoffs.
|
||||
|
||||
### 6-1 - Three-Key Start
|
||||
|
||||
Purpose: First all-network level with one readable problem per network.
|
||||
|
||||
Setup:
|
||||
|
||||
- Networks: `Fuel`, `Coolant`, `Electricity`.
|
||||
- `ReactorControlProp` sits on all three networks.
|
||||
- Required consumers: one `Fuel`, one `Coolant`, one `Electricity`.
|
||||
- One visible `Fuel` leak, one wall-mounted `CoolantSprinklerValve` whose linked `SprinklerControlProp` starts `Enabled`, and one wall `Electricity` leak.
|
||||
- One optional `AllSeeingEyeTerminal` is off the direct route and requires positive `Electricity` flow.
|
||||
- One `HeatShield` and one relevant `RemedySupplyProp` are available for short-term mitigation.
|
||||
|
||||
Timeline:
|
||||
|
||||
1. First choice A: isolate or repair the `Fuel` leak.
|
||||
2. First choice B: disable the linked `SprinklerControlProp` or isolate the sprinkler branch to restore pressure.
|
||||
3. First choice C: isolate or repair the `Electricity` leak before wet cells conduct.
|
||||
4. First choice D: power the `AllSeeingEyeTerminal` branch for later terminal-local `Forecast`.
|
||||
5. First choice E: pick up `HeatShield` or a `RemedySupplyProp` to buy access to one risky intervention.
|
||||
6. Player restores required production for all three carriers.
|
||||
7. Player uses `ActivateReactor`.
|
||||
|
||||
Win: `ReactorReadiness` is true for all three carriers, one required `ConsumerProp` per carrier is `Enabled` and `Producing`, then `ActivateReactor`.
|
||||
|
||||
Lose: terminal `Heat`, `UnsafeEntryLoss` from `Unsafe` cells, wet-electric terminal failure, or other fixed terminal failure.
|
||||
|
||||
ImplementationNotes:
|
||||
|
||||
- Keep this forgiving and compact.
|
||||
- Do not require hazard cleanup after `Ready`.
|
||||
- Inventory mitigation should help with access or timing, not replace network repair and routing.
|
||||
|
||||
### 6-2 - Cascade Lockout
|
||||
|
||||
Purpose: Require ordering because one fix can worsen another surface interaction.
|
||||
|
||||
Setup:
|
||||
|
||||
- Networks: `Fuel`, `Coolant`, `Electricity`.
|
||||
- A `Fuel` leak can meet an `Electricity` leak and `Ignite`.
|
||||
- A wall-mounted `CoolantSprinklerValve` can quench or dilute but creates `SprinklerWater` while its linked `SprinklerControlProp` is `Enabled`.
|
||||
- A powered `DoorProp` can contain one surface spread path.
|
||||
- One `AllSeeingEyeTerminal` is reachable and requires positive `Electricity` flow.
|
||||
- Required consumers: one per carrier.
|
||||
|
||||
Timeline:
|
||||
|
||||
1. First choice A: use the `SprinklerControlProp` to suppress/dilute; `Pulse` creates wet-conduction risk.
|
||||
2. First choice B: isolate electricity before using sprinkler; `Pulse` delays electricity production.
|
||||
3. First choice C: power and toggle the `DoorProp` to change containment.
|
||||
4. First choice D: power the `AllSeeingEyeTerminal` branch for later systemic `Forecast`.
|
||||
5. Player chooses containment, suppression, isolation, and production order.
|
||||
6. Player uses `ActivateReactor`.
|
||||
|
||||
Win: `ReactorReadiness` is true for all three carriers, one required `ConsumerProp` per carrier is `Enabled` and `Producing`, then `ActivateReactor`.
|
||||
|
||||
Lose: terminal `Heat`, `UnsafeEntryLoss` from `Unsafe` cells, wet-electric terminal failure, or other fixed terminal failure.
|
||||
|
||||
ImplementationNotes:
|
||||
|
||||
- This is the main test of previous group lessons in one compact level.
|
||||
- Door operation must depend on local `Electricity` supply.
|
||||
|
||||
### 6-3 - Critical Path
|
||||
|
||||
Purpose: Final capstone with multiple valid solution orders.
|
||||
|
||||
Setup:
|
||||
|
||||
- Networks: `Fuel`, `Coolant`, `Electricity`.
|
||||
- `ReactorControlProp` sits on all three networks.
|
||||
- Two `ConsumerProp` instances per carrier exist; one per carrier is required.
|
||||
- One `Fuel` leak, one wall-mounted `CoolantSprinklerValve` whose linked `SprinklerControlProp` starts `Enabled`, one wall `Electricity` leak, one powered `DoorProp`, and one weakened structural segment.
|
||||
- One powered-route `AllSeeingEyeTerminal`, one `HeatShield`, and one relevant `RemedySupplyProp` are available.
|
||||
|
||||
Timeline:
|
||||
|
||||
1. First choice A: power the `AllSeeingEyeTerminal` branch; a later terminal interaction grants terminal-local topology and `Forecast` while hazards advance.
|
||||
2. First choice B: isolate or repair the `Fuel` leak before `Ignite`.
|
||||
3. First choice C: disable the linked `SprinklerControlProp` or isolate the sprinkler before energizing wet areas.
|
||||
4. First choice D: isolate or repair electricity before using sprinkler suppression.
|
||||
5. First choice E: pick up `HeatShield` or a `RemedySupplyProp` for movement/access safety.
|
||||
6. Player selects a minimum viable producing consumer set and stabilizes the most terminal interaction.
|
||||
7. Player uses `ActivateReactor` as soon as `Ready` appears.
|
||||
|
||||
Win: `ReactorReadiness` is true for all three carriers, one required `ConsumerProp` per carrier is `Enabled` and `Producing`, then `ActivateReactor`.
|
||||
|
||||
Lose: terminal `Heat`, `UnsafeEntryLoss` from `Unsafe` cells, wet-electric terminal failure, or other fixed terminal failure.
|
||||
|
||||
ImplementationNotes:
|
||||
|
||||
- Winning with controlled remaining hazards is valid.
|
||||
- `Forecast` output should come only from fixed systemic rules while at the active and powered terminal.
|
||||
|
||||
## Implementation Checklist
|
||||
|
||||
- Add campaign manifest entries in this document order once level data exists.
|
||||
- Use stable level names because save data and UI can reference them.
|
||||
- Implement one tutorial level first, then one representative level from each group before filling every group.
|
||||
- Prioritize mechanics in this order:
|
||||
1. `Pulse` playback and `ReactorReadiness`.
|
||||
2. `Fuel` source, leak, repair, isolation, and `ActivateReactor`.
|
||||
3. `Coolant` `ConsumerProp`, `SprinklerControlProp`, wall-mounted `CoolantSprinklerValve`, pressure drop, and `Evaporation`.
|
||||
4. `Electricity` wall leaks, `UnsafeEntryLoss`, powered `DoorProp`.
|
||||
5. Powered `AllSeeingEyeTerminal` and terminal-local systemic `Forecast`.
|
||||
6. `Ignite`, `Heat`, `Dilute`, `Quench`, and wet-electric `Conduct`.
|
||||
7. Full campaign content pass.
|
||||
- If scope tightens, ship tutorial, Group 1, one level each from Groups 2-5, and one final Group 6 level.
|
||||
|
||||
## Test Expectations
|
||||
|
||||
Campaign implementation should add tests for:
|
||||
|
||||
- every level loading and validating,
|
||||
- required consumer counts matching intended group mechanics,
|
||||
- tutorial solvable with exactly one `LengthyAction` before `ActivateReactor`,
|
||||
- every non-tutorial level exposing at least two valid first `LengthyAction` choices,
|
||||
- win criteria reachable for each authored level,
|
||||
- no level requiring a wait or fast-forward command,
|
||||
- no `Forecast` visible away from an active and powered `AllSeeingEyeTerminal`,
|
||||
- unpowered `DoorProp` and `AllSeeingEyeTerminal` interactions causing no state change while still triggering a `Pulse`,
|
||||
- no direct interaction with `CoolantSprinklerValve`; sprinkler changes happen through linked `SprinklerControlProp`,
|
||||
- no pulse-only stationary robot hazard loss,
|
||||
- campaign manifest order matching this document.
|
||||
367
docs/UX.md
Normal file
@@ -0,0 +1,367 @@
|
||||
# Reactor Maintenance UX Blueprint
|
||||
|
||||
This document is the Godot implementation blueprint for the player-facing frontend. It describes scene responsibilities, control composition, navigation flow, and the minimum data concepts needed to populate the UI. It is not a final art direction document.
|
||||
|
||||
## UX Goals
|
||||
|
||||
- Put the level grid and current reactor state at the center of play.
|
||||
- Make every simulation state readable before the player commits to a lengthy action.
|
||||
- Keep menu flow short: splash, main menu, mode selection, level, outcome, next action.
|
||||
- Treat campaign as a linear chain of handcrafted levels with names and short flavor text.
|
||||
- Let players retry a lost level from its starting state and advance from a won level to the next campaign level.
|
||||
- Keep one-action setups in guided tutorial content; campaign levels should present at least two plausible lengthy interventions with different pulse outcomes.
|
||||
|
||||
## Scene Flow
|
||||
|
||||
```text
|
||||
SplashScreen
|
||||
-> MainMenu
|
||||
|
||||
MainMenu
|
||||
-> New Campaign -> CampaignIntro or LevelScreen
|
||||
-> Continue Campaign -> LevelScreen
|
||||
-> Play Random Level -> GenerationScreen -> LevelScreen
|
||||
-> OptionsScreen
|
||||
-> TutorialScreen
|
||||
|
||||
LevelScreen
|
||||
-> LoseOverlay -> Retry Level | MainMenu
|
||||
-> WinOverlay -> Next Level | MainMenu
|
||||
-> GameOverScreen
|
||||
-> GameWonScreen
|
||||
|
||||
GameOverScreen
|
||||
-> Retry Current Level | MainMenu
|
||||
|
||||
GameWonScreen
|
||||
-> MainMenu
|
||||
```
|
||||
|
||||
Use one scene navigation service or autoload to own transitions. Individual screens emit intent signals such as `NewCampaignRequested`, `RetryRequested`, or `NextLevelRequested`; they should not load unrelated scenes directly.
|
||||
|
||||
## Shared Runtime Concepts
|
||||
|
||||
### Campaign Manifest
|
||||
|
||||
Represent campaign content as an ordered list. The exact storage format can be a Godot `Resource`, JSON file, or C# model, but the UI should receive these fields:
|
||||
|
||||
- `Id`: stable level identifier.
|
||||
- `Name`: player-facing level name.
|
||||
- `FlavorText`: one or two short lines of lore shown before the level starts and after menu selection.
|
||||
- `LevelPath`: path or key used to load serialized simulation level data.
|
||||
|
||||
The first implementation can ship a single manifest for the default campaign. Branching paths, unlock maps, and difficulty variants are out of scope.
|
||||
|
||||
### Session State
|
||||
|
||||
The frontend session should track:
|
||||
|
||||
- current mode: campaign, random level, or tutorial,
|
||||
- current campaign index when in campaign mode,
|
||||
- current level start snapshot for retry,
|
||||
- current mutable level state during play,
|
||||
- whether a continue save exists,
|
||||
- last outcome: won, lost, campaign complete, or abandoned.
|
||||
|
||||
Retry always restores the current level start snapshot. Continue campaign resumes the last saved campaign index and mutable level state if available.
|
||||
|
||||
## Scene Blueprints
|
||||
|
||||
### SplashScreen.tscn
|
||||
|
||||
Purpose: a short branded entry point before the main menu.
|
||||
|
||||
Recommended root: `Control`.
|
||||
|
||||
Controls:
|
||||
|
||||
- `CenterContainer`
|
||||
- `VBoxContainer`
|
||||
- title label: `Reactor Maintenance`
|
||||
- small status label for build/version text if available
|
||||
|
||||
Behavior:
|
||||
|
||||
- Show for roughly 1-2 seconds.
|
||||
- Any confirm, cancel, click, or tap skips immediately to `MainMenu`.
|
||||
- Do not block on loading campaign data unless a clear loading label is shown.
|
||||
|
||||
### MainMenu.tscn
|
||||
|
||||
Purpose: primary navigation.
|
||||
|
||||
Recommended root: `Control`.
|
||||
|
||||
Controls:
|
||||
|
||||
- title label: `Reactor Maintenance`
|
||||
- vertical command stack:
|
||||
- `New Campaign`
|
||||
- `Continue Campaign`
|
||||
- `Play Random Level`
|
||||
- `Options`
|
||||
- `Tutorial`
|
||||
- small footer for version or build label
|
||||
|
||||
Behavior:
|
||||
|
||||
- `Continue Campaign` is disabled when no continue state exists.
|
||||
- `New Campaign` starts at campaign index `0`; if a save exists, confirm replacement before overwriting.
|
||||
- `Play Random Level` loads a single non-campaign level. It can select from authored levels until random generation exists.
|
||||
- `Options` and `Tutorial` return to this screen.
|
||||
|
||||
### CampaignIntro.tscn
|
||||
|
||||
Purpose: optional lightweight introduction before the first campaign level or before each level.
|
||||
|
||||
Recommended root: `Control`.
|
||||
|
||||
Controls:
|
||||
|
||||
- level name label
|
||||
- flavor text label
|
||||
- `Begin` button
|
||||
- `Back` button when entered from a menu
|
||||
|
||||
Behavior:
|
||||
|
||||
- For a compact first pass, this may be embedded as a modal panel inside `LevelScreen`.
|
||||
- The text should be short enough to read quickly and should not explain mechanics that belong in tutorial content.
|
||||
|
||||
### LevelScreen.tscn
|
||||
|
||||
Purpose: core gameplay screen.
|
||||
|
||||
Recommended root: `Control`.
|
||||
|
||||
Suggested layout:
|
||||
|
||||
```text
|
||||
LevelScreen
|
||||
MarginContainer
|
||||
VBoxContainer
|
||||
LevelHeader
|
||||
HSplitContainer
|
||||
GridViewport
|
||||
InspectorPanel
|
||||
ActionBar
|
||||
OverlayLayer
|
||||
```
|
||||
|
||||
Primary controls:
|
||||
|
||||
- `LevelHeader`
|
||||
- level name
|
||||
- campaign progress label such as `Level 2 / 8`
|
||||
- global state badge: `Stable`, `Caution`, `Critical`, `Ready`
|
||||
- reactor heat or readiness summary
|
||||
- `GridViewport`
|
||||
- visual grid
|
||||
- robot marker
|
||||
- terrain, props, leaks, hazards, and optional underground overlay
|
||||
- selected cell highlight and reachable/actionable hints
|
||||
- `InspectorPanel`
|
||||
- selected cell coordinates
|
||||
- terrain and prop summary
|
||||
- underground values when visible through terminal access
|
||||
- visible hazards, sprinkler water, and wet-electricity risk with safe/caution/critical bands
|
||||
- forecast warnings
|
||||
- `ActionBar`
|
||||
- contextual action buttons: move, interact, toggle isolation valve, activate sprinkler valve, repair, remedy, heat shield, activate reactor
|
||||
- disabled buttons must show why the action is unavailable
|
||||
- `InventoryPanel`
|
||||
- fuel neutralizer count
|
||||
- coolant neutralizer count
|
||||
- electricity neutralizer count
|
||||
- heat shield count
|
||||
|
||||
Behavior:
|
||||
|
||||
- Selection and inspection are quick actions and must not trigger a pulse.
|
||||
- Movement is a quick action and must update robot position without triggering a pulse.
|
||||
- Interact, isolation valve toggles, sprinkler valve activation, repair, remedy, and heat shield are environment-changing actions and must trigger one animated pulse when accepted.
|
||||
- Activate reactor must be visually prominent when ready and should resolve immediately without requiring a wait or extra pulse.
|
||||
- Invalid actions should produce a short refusal message and not mutate level state.
|
||||
- When the level state becomes `Lost`, show `LoseOverlay`.
|
||||
- When the level state becomes `Won`, show `WinOverlay`.
|
||||
- When the reactor is ready, make `Activate Reactor` visually prominent but keep other valid actions available.
|
||||
- Before committing a lengthy action, the selected action state should expose the forecasted pulse consequence in plain terms such as isolated leak, restored pressure, downstream starvation, hazard growth, or reactor ready.
|
||||
|
||||
Pulse playback behavior:
|
||||
|
||||
- During a pulse, animate the configured steps as one short cascade of hazard motion, leak growth, quenching, evaporation, ignition, electrical conduction, and readiness updates.
|
||||
- Isolation valve toggles must visibly mark the affected underground branch, show stopped or restored pressure flow, and warn when downstream consumers or reactor feed will starve.
|
||||
- Sprinkler valve activation must visibly release blue sprinkler water at authored outlet cells and show the affected coolant service area losing pressure.
|
||||
- Evaporation must be communicated through shrinking wet overlays and stronger steam/cooling feedback on hot cells.
|
||||
- Wet cells that can spread electricity must use a distinct warning treatment so the player can distinguish helpful suppression water from dangerous electrified water.
|
||||
- Disable conflicting action commands while pulse playback is running so the player cannot queue hidden actions into an unresolved environment state.
|
||||
- Present the final post-pulse state as the next decision point.
|
||||
- Do not make pulse length vary by action type, forecast outcome, or danger level.
|
||||
- Do not expose a normal campaign fast-forward loop; the player goal is to solve the cascade and activate the reactor as soon as a pulse leaves its needs fulfilled.
|
||||
|
||||
### LoseOverlay.tscn
|
||||
|
||||
Purpose: immediate failure response while preserving context.
|
||||
|
||||
Recommended root: `PanelContainer` or `CanvasLayer` child.
|
||||
|
||||
Controls:
|
||||
|
||||
- title label: `Level Lost`
|
||||
- short cause label from the terminal state or latest failure message
|
||||
- `Retry Level`
|
||||
- `Main Menu`
|
||||
|
||||
Behavior:
|
||||
|
||||
- Pauses input to the grid behind it.
|
||||
- `Retry Level` reloads the current level from the start snapshot.
|
||||
- `Main Menu` abandons the current run after confirmation if unsaved progress would be lost.
|
||||
|
||||
### WinOverlay.tscn
|
||||
|
||||
Purpose: immediate success response and transition to the next level.
|
||||
|
||||
Recommended root: `PanelContainer` or `CanvasLayer` child.
|
||||
|
||||
Controls:
|
||||
|
||||
- title label: `Reactor Online`
|
||||
- level completion text
|
||||
- `Next Level` when another campaign level exists
|
||||
- `Finish Campaign` when this was the final campaign level
|
||||
- `Main Menu`
|
||||
|
||||
Behavior:
|
||||
|
||||
- Saves campaign progress before presenting the next-level command.
|
||||
- `Next Level` loads the next handcrafted campaign level and shows its name and flavor text.
|
||||
- In random level mode, replace `Next Level` with `Play Another` or `Main Menu`.
|
||||
|
||||
### GameOverScreen.tscn
|
||||
|
||||
Purpose: full-screen campaign failure or abandoned-run endpoint.
|
||||
|
||||
Recommended root: `Control`.
|
||||
|
||||
Controls:
|
||||
|
||||
- title label: `Game Over`
|
||||
- summary label with the failed level name
|
||||
- `Retry Current Level`
|
||||
- `Main Menu`
|
||||
|
||||
Behavior:
|
||||
|
||||
- Use this when leaving the level context after a loss, not for the immediate in-level failure modal.
|
||||
- Retry uses the same start snapshot rules as `LoseOverlay`.
|
||||
|
||||
### GameWonScreen.tscn
|
||||
|
||||
Purpose: full-screen campaign completion endpoint.
|
||||
|
||||
Recommended root: `Control`.
|
||||
|
||||
Controls:
|
||||
|
||||
- title label: `Campaign Complete`
|
||||
- short final lore text
|
||||
- completed level count
|
||||
- `Main Menu`
|
||||
|
||||
Behavior:
|
||||
|
||||
- Reached after winning the final handcrafted campaign level.
|
||||
- Clears any in-progress continue marker or marks campaign complete.
|
||||
|
||||
### OptionsScreen.tscn
|
||||
|
||||
Purpose: global settings.
|
||||
|
||||
Recommended root: `Control`.
|
||||
|
||||
Controls:
|
||||
|
||||
- audio sliders
|
||||
- fullscreen toggle
|
||||
- UI scale selector if supported
|
||||
- color/accessibility toggles when implemented
|
||||
- `Back`
|
||||
|
||||
Behavior:
|
||||
|
||||
- Changes apply immediately where practical.
|
||||
- Persist settings separately from campaign progress.
|
||||
|
||||
### TutorialScreen.tscn
|
||||
|
||||
Purpose: teach interaction grammar before or outside campaign.
|
||||
|
||||
Recommended root: `Control`.
|
||||
|
||||
Controls:
|
||||
|
||||
- tutorial topic list
|
||||
- content panel
|
||||
- `Start Tutorial Level`
|
||||
- `Back`
|
||||
|
||||
Behavior:
|
||||
|
||||
- Keep tutorial text focused on action economy, pulses, isolation valves, sprinkler suppression, evaporation, wet-electricity hazards, forecasts, remedies, and reactor activation.
|
||||
- Tutorial level can reuse `LevelScreen` with a tutorial mode flag and guided prompts.
|
||||
- A tutorial level may demonstrate a single safe lengthy action. The first campaign level should not be a single-action demonstration; it should ask the player to choose between service restoration, isolation, repair, or activation timing.
|
||||
|
||||
## Reusable Controls
|
||||
|
||||
- `PrimaryButton`: consistent command button style and focus behavior.
|
||||
- `StateBadge`: color-coded label for `Stable`, `Caution`, `Critical`, `Ready`, `Lost`, and `Won`.
|
||||
- `LevelHeader`: level metadata, global state, and campaign progress.
|
||||
- `CellInspector`: selected cell details, visible hazards, sprinkler water, wet-electricity risk, prop state, isolation valve branch impact, underground details when available.
|
||||
- `ForecastList`: ordered warnings from soonest to latest pulse.
|
||||
- `InventoryStrip`: remedy and heat shield counts.
|
||||
- `OutcomeOverlay`: shared base layout for win and loss overlays.
|
||||
- `ConfirmDialog`: save overwrite, abandon run, and return-to-menu confirmations.
|
||||
|
||||
Keep repeated controls as separate scenes so the level screen, tutorial, and overlays can reuse them without duplicating layout logic.
|
||||
|
||||
## Input And Focus
|
||||
|
||||
- Support mouse and keyboard from the first implementation.
|
||||
- Confirm activates the focused button or selected contextual action.
|
||||
- Cancel closes overlays or returns to the previous screen when safe.
|
||||
- Arrow keys or WASD can move the robot when the level grid has focus.
|
||||
- Tab cycles between grid, inspector, action bar, and menu buttons.
|
||||
- Disabled commands must remain inspectable by focus or hover so the player can read why they are unavailable.
|
||||
|
||||
## Visual Language
|
||||
|
||||
- The game should feel technical, readable, and tense, not decorative.
|
||||
- Use restrained panels and clear hierarchy: grid first, status second, supporting details third.
|
||||
- Color roles:
|
||||
- fuel: red
|
||||
- coolant/sprinkler water: blue
|
||||
- electricity: yellow
|
||||
- heat: orange or white-hot accent
|
||||
- isolated branch: muted carrier color with a broken-flow or valve marker
|
||||
- wet-electricity risk: yellow over blue or a distinct charged-water pattern
|
||||
- safe: neutral or green
|
||||
- caution: amber
|
||||
- critical/lost: red
|
||||
- ready/won: bright green or white
|
||||
- Do not rely on color alone; pair colored states with text labels or icons.
|
||||
|
||||
## Implementation Acceptance Checklist
|
||||
|
||||
- Every scene has one clear root responsibility and emits navigation intent instead of directly owning campaign policy.
|
||||
- The main menu exposes all requested buttons.
|
||||
- Campaign levels show `Name` and `FlavorText`.
|
||||
- Lost levels can be retried from the original starting snapshot.
|
||||
- Won levels can advance to the next campaign level.
|
||||
- Final campaign win reaches `GameWonScreen`.
|
||||
- Full failure endpoint reaches `GameOverScreen`.
|
||||
- The level screen distinguishes quick actions from lengthy actions.
|
||||
- The level screen previews selected lengthy-action pulse consequences before commitment.
|
||||
- The level screen makes isolation valve branch effects, sprinkler valve outlets, evaporation, and wet-electricity risk readable before and after a pulse.
|
||||
- Forecasts, inventory, selected cell inspection, and global level state have dedicated UI space.
|
||||
- Documentation stays aligned with `docs/design.md` when simulation rules change.
|
||||
336
docs/design.md
@@ -8,55 +8,111 @@ The simulation core is built from:
|
||||
|
||||
- static floor and wall terrain,
|
||||
- underground fuel, coolant, and electricity networks,
|
||||
- surface props for controls, terminals, supplies, doors, and reactor activation,
|
||||
- surface props for sources, isolation valves, controls, terminals, supplies, doors, and reactor activation,
|
||||
- consumers that consume whichever underground services exist under their cell,
|
||||
- reachable leaks that project hazards onto floor cells,
|
||||
- reachable leaks and sprinkler valves that project hazards or suppression water onto floor cells,
|
||||
- transport network structural integrity,
|
||||
- deterministic fixed simulation rules and forecasts.
|
||||
|
||||
The game should feel logical, tactical, readable, and systemic. It should avoid randomness, action pressure, and hidden information once the player reaches an all-seeing-eye terminal.
|
||||
The game should feel logical, tactical, readable, and systemic. It should avoid randomness, action pressure, and hidden information while the player is using an active and powered `AllSeeingEyeTerminal`.
|
||||
|
||||
## Terminology
|
||||
|
||||
Design and campaign documents use these PascalCase keywords when referencing shared mechanics:
|
||||
|
||||
- `Fuel`, `Coolant`, and `Electricity`: underground carrier networks.
|
||||
- `Heat`, `LeakedFuel`, `SprinklerWater`, and `LeakedElectricity`: surface values stored on floor cells.
|
||||
- `Unsafe`: derived floor-cell movement state caused by dangerous surface values or value combinations.
|
||||
- `Step`: one low-level deterministic simulation iteration.
|
||||
- `Pulse`: the player-facing environment response after one accepted lengthy action.
|
||||
- `QuickAction`: a player action that does not mutate the environment and does not trigger a `Pulse`.
|
||||
- `LengthyAction`: a player action that mutates the environment and triggers one `Pulse`.
|
||||
- `MoveRobot`: the quick action that moves the robot to an adjacent floor cell.
|
||||
- `InteractProp`, `InteractLeak`, `ApplyHeatShield`, and `ActivateReactor`: player commands that can be accepted or refused by fixed rules.
|
||||
- `FlowProp`, `IsolationValveProp`, `ConsumerProp`, `JunctionProp`, `DoorProp`, `AllSeeingEyeTerminal`, `RemedySupplyProp`, `SprinklerControlProp`, `CoolantSprinklerValve`, and `ReactorControlProp`: authored surface prop categories.
|
||||
- `PoweredProp`: a prop that requires positive local `Electricity` flow before its interaction can take effect.
|
||||
- `Enabled`, `Disabled`, `Open`, `Closed`, `Supplied`, `Starved`, and `Producing`: prop or service states.
|
||||
- `Forecast`: a systemic warning generated by deterministic simulation over copied state.
|
||||
- `ReactorReadiness`: the derived condition that allows `ActivateReactor`.
|
||||
- `Stable`, `Caution`, `Critical`, `Ready`, `Lost`, and `Won`: derived level states.
|
||||
- `UnsafeEntryLoss`: terminal loss caused when `MoveRobot` enters an `Unsafe` destination cell.
|
||||
|
||||
## Action Economy
|
||||
|
||||
There is no per-turn action budget. Player choices are either quick or lengthy.
|
||||
There is no action budget. Player choices are either quick or lengthy.
|
||||
|
||||
Quick actions do not mutate the level state and do not advance the simulation:
|
||||
`QuickAction` commands do not mutate the environment and do not trigger a `Pulse`:
|
||||
|
||||
- `MoveRobot`: move the robot to an adjacent floor cell, reduce heat immunity movement steps if applicable, and reject movement into walls or out of bounds,
|
||||
- `MoveRobot`: move the robot to an adjacent floor cell, reduce heat immunity movement steps if applicable, reject movement into walls or out of bounds, and apply `UnsafeEntryLoss` if the destination floor cell is `Unsafe`,
|
||||
- selection and inspection: change only UI selection state,
|
||||
- all-seeing-eye viewing: when the robot is at an all-seeing-eye terminal, allow the player to view every surface and underground layer.
|
||||
- `AllSeeingEyeTerminal` viewing: when the robot is at an active and powered terminal, allow the player to view every surface and underground layer.
|
||||
|
||||
Lengthy actions mutate level state and immediately advance one simulation step:
|
||||
`LengthyAction` commands commit an intervention and immediately trigger one `Pulse`:
|
||||
|
||||
- `InteractProp`: toggle flow props, toggle consumers, cycle junction ratios, open or close doors, pick up remedy supplies, or activate all-seeing-eye viewing from a terminal,
|
||||
- `InteractProp`: toggle `FlowProp` state, toggle `IsolationValveProp` state, toggle `ConsumerProp` state, cycle `JunctionProp` ratios, open or close powered `DoorProp` instances, pick up `RemedySupplyProp` instances, toggle `SprinklerControlProp` state, or activate powered `AllSeeingEyeTerminal` access from a terminal,
|
||||
- `InteractLeak`: repair a reachable leak or apply a matching elemental remedy,
|
||||
- `ApplyHeatShield`: spend one heat shield and set heat immunity movement steps,
|
||||
- `ActivateReactor`: activate a ready reactor at the current reactor control prop,
|
||||
- `Wait`: advance one simulation step without applying another player mutation.
|
||||
- `ActivateReactor`: activate a ready reactor at the current reactor control prop.
|
||||
|
||||
Invalid actions report refusal and do not mutate gameplay state.
|
||||
|
||||
`DoorProp` and `AllSeeingEyeTerminal` are `PoweredProp` instances. If the robot uses `InteractProp` on an unpowered `PoweredProp`, the action is accepted as a `LengthyAction`, changes no prop state, provides no terminal view, and still triggers one `Pulse`. This makes power loss an authored puzzle consequence rather than a UI refusal. Other invalid interactions remain refused and do not trigger a `Pulse`.
|
||||
|
||||
## Pulses And Steps
|
||||
|
||||
A step is the low-level deterministic simulation iteration. One step injects leaks, resolves same-cell reactions, resolves adjacent surface flow, accumulates deltas, and clamps visible surface values.
|
||||
|
||||
A pulse is the player-facing environment response after an accepted lengthy action. One pulse contains a fixed balance-defined number of steps and is animated as a short cascade. The pulse length is the same for every lengthy action and must not vary by action type, forecast outcome, or current danger level.
|
||||
|
||||
Movement, inspection, and layer viewing do not trigger pulses. The campaign loop should not rely on a player-facing wait or fast-forward command; the puzzle is to solve cascading failures and activate the reactor as soon as a pulse leaves its needs fulfilled. Editor, debug, and automated-test tools may still advance isolated steps or pulses when needed.
|
||||
|
||||
## Goal And Failure
|
||||
|
||||
Each reactor starts offline. A reactor becomes ready when:
|
||||
Each reactor starts offline. `ReactorReadiness` is true and the level enters `Ready` when:
|
||||
|
||||
- every underground network present beneath the reactor control cell has positive amount and intensity,
|
||||
- the level has the required number of enabled, fed, producing fuel consumers,
|
||||
- the level has the required number of enabled, fed, producing coolant consumers,
|
||||
- the level has the required number of enabled, fed, producing electricity consumers,
|
||||
- reactor heat is below the terminal condition.
|
||||
- every underground network present beneath the `ReactorControlProp` cell has positive amount and positive pressure or voltage,
|
||||
- the level has the required number of `Enabled` and `Producing` `Fuel` consumers,
|
||||
- the level has the required number of `Enabled` and `Producing` `Coolant` consumers,
|
||||
- the level has the required number of `Enabled` and `Producing` `Electricity` consumers,
|
||||
- `Heat` is below the terminal condition.
|
||||
|
||||
The required consumer counts are level properties. The reactor is not explicitly bound to any consumer positions.
|
||||
|
||||
When a reactor is ready, the level shows `REACTOR READY`. The player wins by activating the ready reactor at the reactor control site.
|
||||
When `ReactorReadiness` is true, the level shows `REACTOR READY`. The player wins by using `ActivateReactor` at the `ReactorControlProp`, changing the level from `Ready` to `Won`.
|
||||
|
||||
The level is lost when:
|
||||
|
||||
- reactor heat reaches the terminal threshold,
|
||||
- the robot occupies an unsafe final hazard state without applicable protection,
|
||||
- `Heat` reaches the terminal threshold,
|
||||
- `MoveRobot` enters an `Unsafe` destination floor cell without applicable protection, causing `UnsafeEntryLoss`,
|
||||
- fixed simulation rules mark terminal failure.
|
||||
|
||||
Consumer starvation blocks readiness but does not directly cause loss.
|
||||
Missing `ReactorReadiness`, including missing `ReactorControlProp` feed or missing required `ConsumerProp` production, blocks `Ready` but does not directly cause `Lost`.
|
||||
|
||||
## Campaign Progression
|
||||
|
||||
The campaign grows by teaching one intervention grammar at a time. A guided tutorial may use a one-action setup to teach pulse playback, but campaign levels should not rely on a single obvious lengthy action. Every non-tutorial campaign level should present at least two plausible first interventions whose pulses create different readable consequences.
|
||||
|
||||
Early levels can still use one carrier, but they need a real systems choice:
|
||||
|
||||
- feed the service now and risk worsening a pressure-fed fault,
|
||||
- isolate the damaged branch and temporarily disable a required feed or required `ConsumerProp` production,
|
||||
- repair the leak while the root pressure remains unresolved,
|
||||
- reroute or rebalance the network before restoring service.
|
||||
|
||||
Campaign authoring should answer two questions for each level:
|
||||
|
||||
- What are the plausible first lengthy actions?
|
||||
- What does each resulting pulse visibly improve, worsen, or reveal?
|
||||
|
||||
Recommended progression:
|
||||
|
||||
1. Tutorial: one safe guided action may teach that lengthy actions trigger pulses.
|
||||
2. Group 1: `Fuel` source, pressure-fed leak, `IsolationValveProp`, repair site, and direct `ReactorReadiness`.
|
||||
3. Group 2: `Coolant` `ConsumerProp` production, wall-mounted `CoolantSprinklerValve`, pressure recovery, `SprinklerWater`, and `Evaporation`.
|
||||
4. Group 3: `Electricity` wall leaks, powered `DoorProp`, and first `AllSeeingEyeTerminal`.
|
||||
5. Group 4: `Fuel` + `Electricity` interactions where `Ignite` creates recurring `Heat`.
|
||||
6. Group 5: `Coolant` + `Electricity` interactions where wet cells can `Conduct`.
|
||||
7. Group 6: full three-network startup with fire, suppression, wet conduction, and terminal-local `Forecast`.
|
||||
|
||||
## Information
|
||||
|
||||
@@ -65,14 +121,14 @@ The player can always inspect:
|
||||
- surface terrain,
|
||||
- surface props and visible prop state,
|
||||
- visible leaks and repair faces,
|
||||
- visible surface hazards,
|
||||
- visible surface values and `Unsafe` movement cells,
|
||||
- door state,
|
||||
- remedy inventory and supply props,
|
||||
- consumer state: disabled, starved, supplied, producing, or unknown,
|
||||
- level state,
|
||||
- forecasted warnings the simulation can prove.
|
||||
- `Forecast` warnings only while the robot is at an active and powered `AllSeeingEyeTerminal`.
|
||||
|
||||
Underground topology and numeric network values are available through all-seeing-eye viewing after the robot visits an all-seeing-eye terminal. There is no persistent lock or unlock state in the level data.
|
||||
Underground topology and numeric network values are available only while the robot is at an active and powered `AllSeeingEyeTerminal`. There is no persistent lock or unlock state in the level data.
|
||||
|
||||
The editor always sees and authors every layer.
|
||||
|
||||
@@ -87,7 +143,7 @@ Each map coordinate contains:
|
||||
- zero or one underground coolant cell,
|
||||
- zero or one underground electricity cell,
|
||||
- zero or one surface prop,
|
||||
- visible hazard amounts on floor cells,
|
||||
- visible surface values on floor cells,
|
||||
- optionally the robot, only on a floor cell.
|
||||
|
||||
Terrain is authored and does not change during play. Wall cells are not walkable and do not store surface hazards.
|
||||
@@ -113,7 +169,7 @@ Same-carrier underground cells connect by inferred cardinal adjacency.
|
||||
Surface floor cells store:
|
||||
|
||||
- leaked fuel,
|
||||
- leaked coolant,
|
||||
- sprinkler water from coolant discharge,
|
||||
- leaked electricity,
|
||||
- heat,
|
||||
- active elemental remedy blocks.
|
||||
@@ -136,11 +192,14 @@ The derived level states are:
|
||||
Surface prop categories:
|
||||
|
||||
- flow prop,
|
||||
- isolation valve prop,
|
||||
- consumer prop,
|
||||
- junction prop,
|
||||
- door prop,
|
||||
- all-seeing-eye terminal prop,
|
||||
- `AllSeeingEyeTerminal` prop,
|
||||
- remedy supply prop,
|
||||
- sprinkler control prop,
|
||||
- coolant sprinkler valve prop,
|
||||
- reactor control prop.
|
||||
|
||||
Props exist on floor cells. Props do not directly participate in the surface hazard pair table.
|
||||
@@ -149,7 +208,15 @@ Props exist on floor cells. Props do not directly participate in the surface haz
|
||||
|
||||
A flow prop is bound to fuel, coolant, or electricity. It can be `Enabled` or `Disabled`.
|
||||
|
||||
During network flow, an enabled flow prop injects source carrier amount and pressure or voltage into its connected underground network cell. A disabled flow prop injects nothing.
|
||||
During network flow, an enabled flow prop injects source carrier amount and pressure or voltage into its connected underground network cell. A disabled flow prop injects nothing. Flow props are source feeders; use isolation valves for branch shutoffs so startup, shutdown, and damaged-branch isolation stay readable as separate player decisions.
|
||||
|
||||
### Isolation Valve Props
|
||||
|
||||
An isolation valve prop is bound to fuel, coolant, or electricity and sits on a floor cell whose coordinate has exactly one matching underground carrier. It can be `Open` or `Closed`.
|
||||
|
||||
During network flow, an open isolation valve allows carrier propagation through the underground branch normally. A closed isolation valve blocks intentional network propagation across that underground cell's connected branch boundary. Closing a valve can stop pressure or voltage from reaching a damaged segment, but it can also prevent downstream `ConsumerProp` production, `CoolantSprinklerValve` discharge, or `ReactorControlProp` feed.
|
||||
|
||||
Toggling an isolation valve is a lengthy action and triggers one pulse. Inspection should make visible branch effects readable before the player commits. If the robot is at an active and powered `AllSeeingEyeTerminal`, systemic `Forecast` output should also warn about affected underground branches, isolated leaks, and downstream service loss.
|
||||
|
||||
### Consumer Props
|
||||
|
||||
@@ -159,7 +226,7 @@ An enabled consumer derives one service state per underground network present be
|
||||
|
||||
- `Supplied`: enough carrier and pressure or voltage reaches the underground cell.
|
||||
- `Starved`: supply predicates fail.
|
||||
- `Producing`: the consumer was supplied this simulation step and emits service.
|
||||
- `Producing`: the consumer was supplied during the current pulse and emits service.
|
||||
|
||||
A disabled consumer consumes nothing, produces nothing, and cannot satisfy reactor readiness. A consumer on no underground layer is valid but produces no service and contributes no readiness requirement. A consumer on one underground layer consumes that service. A consumer on multiple underground layers consumes all present layers and can satisfy one requirement for each carrier that is producing.
|
||||
|
||||
@@ -167,15 +234,15 @@ The aggregate consumer prop still exposes a single switch state. Per-carrier ser
|
||||
|
||||
### Reactor Control Props
|
||||
|
||||
A reactor control prop is the activation site for one reactor. Reactor readiness is derived from level-level consumer count requirements and the networks beneath the reactor control cell.
|
||||
A `ReactorControlProp` is the activation site for one reactor. `ReactorReadiness` is derived from level-level consumer count requirements and the networks beneath the `ReactorControlProp` cell.
|
||||
|
||||
The reactor control prop itself is not bound to any individual consumer. It is considered a local consumer for any underground network present beneath its cell:
|
||||
The `ReactorControlProp` itself is not bound to any individual consumer. It has a local feed requirement for any underground network present beneath its cell:
|
||||
|
||||
- if no underground layer is present beneath the reactor, the local reactor feed requirement is satisfied,
|
||||
- if no underground layer is present beneath the `ReactorControlProp`, the local `ReactorControlProp` feed requirement is satisfied,
|
||||
- if one or more underground layers are present, every present layer must have positive amount and positive intensity after network propagation,
|
||||
- a present but starved reactor-under-network blocks readiness but does not directly lose the level.
|
||||
- a present but unfed reactor-under-network blocks `ReactorReadiness` but does not directly lose the level.
|
||||
|
||||
Level properties define `RequiredFuelConsumers`, `RequiredCoolantConsumers`, and `RequiredElectricityConsumers`. For each carrier, readiness requires at least that many enabled consumer props whose per-carrier service state is `Producing`.
|
||||
Level properties define `RequiredFuelConsumers`, `RequiredCoolantConsumers`, and `RequiredElectricityConsumers`. For each carrier, `ReactorReadiness` requires at least that many `Enabled` `ConsumerProp` instances whose per-carrier service state is `Producing`.
|
||||
|
||||
### Junction Props
|
||||
|
||||
@@ -183,25 +250,63 @@ A junction prop must be on a floor cell whose coordinate has exactly one undergr
|
||||
|
||||
The engine infers incoming and outgoing branch directions from valid network topology and enabled source paths. A valid junction has one incoming branch and either two or three outgoing branches. Ambiguous junction flow is invalid. Ratio numbers are balance-defined weights that divide carrier amount and pressure or voltage. A zero-weight branch receives no intentional outflow.
|
||||
|
||||
### Powered Props
|
||||
|
||||
A `PoweredProp` requires positive local `Electricity` amount and positive voltage before its authored interaction can take effect. For the approved campaign scope, only `DoorProp` and `AllSeeingEyeTerminal` are `PoweredProp` instances.
|
||||
|
||||
When the robot uses `InteractProp` on an unpowered `PoweredProp`:
|
||||
|
||||
- the action is accepted as a `LengthyAction`,
|
||||
- the prop state does not change,
|
||||
- an unpowered `AllSeeingEyeTerminal` does not reveal underground layers or `Forecast` output,
|
||||
- one `Pulse` resolves normally.
|
||||
|
||||
The prop remains in its previous physical or active state when power is lost. This keeps power-routing consequences readable and allows levels to use wasted powered-prop interactions as deliberate risk.
|
||||
|
||||
### Doors
|
||||
|
||||
A door is a prop on one floor cell. Its orientation is derived from opposing wall cells:
|
||||
A `DoorProp` is a `PoweredProp` on one floor cell. Its orientation is derived from opposing wall cells:
|
||||
|
||||
- north and south walls mean the door sits in an east-west corridor and blocks west/east propagation while closed,
|
||||
- west and east walls mean the door sits in a north-south corridor and blocks north/south propagation while closed.
|
||||
|
||||
A door must have exactly one valid opposing wall pair. Closed doors block fuel, coolant, electricity, and heat propagation across the corridor cell. They do not block robot movement, underground network flow, source feeding, consumer supply, or hazards already present on either side.
|
||||
A `DoorProp` must have exactly one valid opposing wall pair and exactly one connected `Electricity` underground cell at its coordinate. The player can toggle a `DoorProp` only when that local `Electricity` feed has positive amount and positive voltage. If power is lost, the `DoorProp` keeps its last physical `Open` or `Closed` state. Interacting with an unpowered `DoorProp` changes nothing and triggers one `Pulse`.
|
||||
|
||||
Closed `DoorProp` instances block `LeakedFuel`, `SprinklerWater`, `LeakedElectricity`, and `Heat` propagation across the corridor cell. They do not block `MoveRobot`, underground network flow, source feeding, consumer supply, or hazards already present on either side.
|
||||
|
||||
Door blocking is evaluated by the door cell and its inferred corridor axis:
|
||||
|
||||
- east-west corridor doors block surface interaction between the west neighbor, the door cell, and the east neighbor while closed,
|
||||
- north-south corridor doors block surface interaction between the north neighbor, the door cell, and the south neighbor while closed,
|
||||
- open doors do not block surface interaction,
|
||||
- door props on invalid terrain or with ambiguous opposing walls are validation errors.
|
||||
- east-west corridor `DoorProp` instances block surface interaction between the west neighbor, the door cell, and the east neighbor while `Closed`,
|
||||
- north-south corridor `DoorProp` instances block surface interaction between the north neighbor, the door cell, and the south neighbor while `Closed`,
|
||||
- `Open` `DoorProp` instances do not block surface interaction,
|
||||
- `DoorProp` instances on invalid terrain, with ambiguous opposing walls, or without local `Electricity` are validation errors.
|
||||
|
||||
### Sprinkler Controls And Coolant Sprinkler Valves
|
||||
|
||||
A `SprinklerControlProp` is an interactive floor prop linked to exactly one wall-mounted `CoolantSprinklerValve`. It can be `Enabled` or `Disabled` and does not require `Electricity`.
|
||||
|
||||
Toggling a `SprinklerControlProp` is a `LengthyAction` and triggers one `Pulse`. The linked `CoolantSprinklerValve` mirrors the control state for discharge purposes. `CoolantSprinklerValve` instances are not directly interactive by the robot.
|
||||
|
||||
A `CoolantSprinklerValve` is a wall-mounted surface prop that intentionally releases `Coolant` as `SprinklerWater` onto one adjacent authored floor outlet/access face. It exists to let the player trade local fire suppression for reduced `Coolant` service.
|
||||
|
||||
Valve behavior:
|
||||
|
||||
- the valve discharges only while its linked `SprinklerControlProp` is `Enabled`,
|
||||
- an `Enabled` valve adds a balance-defined amount of `SprinklerWater` to its outlet floor cell during each `Step`,
|
||||
- the valve must be authored on a wall cell with exactly one adjacent valid floor outlet/access face,
|
||||
- the valve must be connected to a present `Coolant` network cell,
|
||||
- the valve must be linked to exactly one `SprinklerControlProp`,
|
||||
- discharge creates a local `Coolant` pressure drop and can prevent nearby or downstream `ConsumerProp` production or `ReactorReadiness`,
|
||||
- disabling the linked `SprinklerControlProp` or closing an upstream `IsolationValveProp` can restore `Coolant` pressure on later pulses,
|
||||
- discharge does not repair coolant pipe damage and does not permanently disable the `Coolant` network.
|
||||
|
||||
The local pressure drop is deterministic and spatial. It should be derived from the valve's connected `Coolant` network branch so that the player can understand why nearby consumers or `ReactorReadiness` become insufficient after sprinkler discharge.
|
||||
|
||||
### Terminals And Supplies
|
||||
|
||||
An all-seeing-eye terminal allows full underground inspection when visited.
|
||||
An `AllSeeingEyeTerminal` is a `PoweredProp` that allows full underground inspection and `Forecast` viewing while the robot is at an active and powered terminal. Activating a powered terminal is a `LengthyAction` and triggers one `Pulse`; viewing is terminal-local and does not persist after the robot leaves.
|
||||
|
||||
Interacting with an unpowered `AllSeeingEyeTerminal` changes nothing, reveals no underground layers or `Forecast` output, and still triggers one `Pulse`.
|
||||
|
||||
Remedy supply props are single-use pickups:
|
||||
|
||||
@@ -212,16 +317,20 @@ Remedy supply props are single-use pickups:
|
||||
|
||||
Each supply provides one matching inventory item and then becomes depleted.
|
||||
|
||||
## Leaks And Remedies
|
||||
## Leaks, Sprinklers, And Remedies
|
||||
|
||||
Each leak stores carrier type, underground coordinate, accessible floor coordinate, and repair state.
|
||||
|
||||
Fuel and coolant leaks:
|
||||
Leaks are pressure-fed or voltage-fed faults. A leaking underground cell remains damaged until repaired, but fresh surface injection only occurs when the leaking cell currently receives positive amount and positive pressure or voltage from network propagation. Isolating a leak therefore stops new hazard growth without repairing the underlying fault. Repair removes the fault and restores structural integrity, but it does not clean hazards already on the surface.
|
||||
|
||||
Fuel leaks:
|
||||
|
||||
- occur under floor cells,
|
||||
- use the same coordinate as their accessible floor coordinate,
|
||||
- can be repaired or remediated by the robot standing on that floor cell.
|
||||
|
||||
Coolant pipe failures use the same reachable-access rules as fuel leaks for repair, but their surface output is treated as sprinkler water rather than as a generic damaging liquid. Authored puzzles should prefer coolant sprinkler valves for intentional player-triggered coolant release.
|
||||
|
||||
Electricity leaks:
|
||||
|
||||
- occur in wall cells,
|
||||
@@ -229,7 +338,7 @@ Electricity leaks:
|
||||
- can be repaired or remediated from that floor cell,
|
||||
- emit only to that stored face.
|
||||
|
||||
All leaks must have valid floor access. Repair changes the underground cell from `Leaking` to `Intact`, restores structural integrity to max, and stops future injection. Repair does not clean existing surface hazards.
|
||||
All leaks must have valid floor access. Repair changes the underground cell from `Leaking` to `Intact`, restores structural integrity to max, and stops future injection. Repair does not clean existing surface hazards. If the repaired segment is immediately returned to excessive pressure or voltage, structural integrity rules may damage it again on later pulses.
|
||||
|
||||
The robot carries remedial consumables with balance-defined inventory capacity:
|
||||
|
||||
@@ -251,9 +360,9 @@ For each carrier:
|
||||
1. Clear transient carrier amount and pressure or voltage.
|
||||
2. Start from every enabled flow prop connected to that carrier.
|
||||
3. Walk through connected intact and leaking underground cells.
|
||||
4. Stop at absent cells and disconnected topology.
|
||||
4. Stop at absent cells, closed isolation valve boundaries, and disconnected topology.
|
||||
5. Apply distance falloff.
|
||||
6. Apply valid junction ratio weights.
|
||||
6. Apply valid junction ratio weights and isolation valve branch blocking.
|
||||
7. Assign each reached cell its best incoming carrier amount and best incoming pressure or voltage.
|
||||
8. Clamp final values.
|
||||
|
||||
@@ -263,28 +372,45 @@ A consumer is supplied when carrier amount, pressure or voltage, and connectivit
|
||||
|
||||
## Surface Hazards
|
||||
|
||||
Leaking underground cells remain part of network propagation.
|
||||
Leaking underground cells remain part of network propagation unless isolated by a closed valve.
|
||||
|
||||
During leak injection:
|
||||
|
||||
- fuel leaks add leaked fuel to the accessible floor cell,
|
||||
- coolant leaks add leaked coolant to the accessible floor cell,
|
||||
- electricity leaks add leaked electricity to the stored floor emission face,
|
||||
- fed fuel leaks add leaked fuel to the accessible floor cell,
|
||||
- fed coolant pipe failures and `CoolantSprinklerValve` instances with enabled linked `SprinklerControlProp` instances add sprinkler water to valid floor outlet/access cells,
|
||||
- fed electricity leaks add leaked electricity to the stored floor emission face,
|
||||
- active matching remedy blocks prevent matching element entry.
|
||||
|
||||
Injection magnitude is balance data and may depend on local carrier amount, pressure, or voltage. If the target floor cell has an active matching remedy block, that matching element is not injected into the cell for that step. Remedy blocks do not block other elements or heat.
|
||||
|
||||
After injection, the engine evaluates local interactions between leaked fuel, leaked coolant, leaked electricity, and heat on the same floor cell and across unblocked adjacent floor cells.
|
||||
After injection and sprinkler discharge, the engine evaluates local interactions between leaked fuel, sprinkler water, leaked electricity, and heat on the same floor cell and across unblocked adjacent floor cells.
|
||||
|
||||
Surface interaction resolution is deterministic:
|
||||
|
||||
- same-cell interactions evaluate every unordered quantity pair on each floor cell,
|
||||
- coolant mitigation evaluates before ignition and electrical spread on each floor cell,
|
||||
- same-cell interactions then evaluate every unordered quantity pair on each floor cell,
|
||||
- adjacent interactions evaluate every unordered pair of adjacent floor cells once,
|
||||
- same-carrier leaked fuel, coolant, electricity, and heat equalize across adjacent floor cells using `Flow(amount)`,
|
||||
- same-carrier leaked fuel, sprinkler water, electricity, and heat equalize across adjacent floor cells using `Flow(amount)`,
|
||||
- wet floor cells spread electricity faster than dry floor cells,
|
||||
- sprinkler water evaporates every step using ambient evaporation plus heat-driven evaporation,
|
||||
- wall cells never store surface hazards and do not participate,
|
||||
- closed doors and remedy blocks gate the interactions they explicitly block,
|
||||
- deltas accumulate during an interaction pass and are applied together before clamping.
|
||||
|
||||
## Unsafe Movement
|
||||
|
||||
`Unsafe` is a derived movement-safety state on floor cells. It is recalculated from current surface values after authored setup and after each `Pulse`.
|
||||
|
||||
A floor cell is `Unsafe` when any of these conditions are true:
|
||||
|
||||
- `Heat` is at or above the balance-defined unsafe heat threshold,
|
||||
- `LeakedElectricity` is at or above the balance-defined unsafe electricity threshold,
|
||||
- `SprinklerWater` and `LeakedElectricity` together meet the balance-defined wet-electric unsafe rule.
|
||||
|
||||
`LeakedFuel` alone is not `Unsafe`. It is dangerous because it can `Ignite` with `Heat` or `LeakedElectricity`. `SprinklerWater` alone is not `Unsafe`.
|
||||
|
||||
`UnsafeEntryLoss` occurs only when `MoveRobot` enters an `Unsafe` destination cell without applicable protection. A `Pulse` does not cause `UnsafeEntryLoss` for a robot that remains stationary on a cell that becomes `Unsafe`; the loss happens when the player next attempts to enter an `Unsafe` destination. `HeatShield` can protect only against `Heat` entry according to its configured duration. Element neutralizers can remove their matching surface element before movement, but they do not provide general immunity.
|
||||
|
||||
## Hazard Bands And Pair Table
|
||||
|
||||
Balance thresholds project numeric values into safe, caution, and critical bands:
|
||||
@@ -298,19 +424,21 @@ The pair table maps projected bands to parameterized verbs:
|
||||
|
||||
- `Hold`: no direct change,
|
||||
- `Flow(amount)`: equalize a surface quantity by a balance-defined transfer amount,
|
||||
- `Dilute(amount)`: reduce fuel by a balance-defined amount,
|
||||
- `Warm(amount)`: increase heat by a balance-defined amount,
|
||||
- `Quench(amount)`: reduce heat by a balance-defined amount,
|
||||
- `Short(heat, discharge)`: add heat and discharge electricity by balance-defined amounts,
|
||||
- `Evaporate(amount, cooling)`: reduce sprinkler water and reduce heat by balance-defined amounts,
|
||||
- `Conduct(multiplier)`: spread electricity through wet floor cells faster than normal adjacent electrical flow,
|
||||
- `Ignite(heat, fuel)`: add heat and consume fuel by balance-defined amounts.
|
||||
|
||||
| Row\Col | FuelSafe | FuelCaution | FuelCritical | CoolantSafe | CoolantCaution | CoolantCritical | ElectricitySafe | ElectricityCaution | ElectricityCritical | HeatSafe | HeatCaution | HeatCritical |
|
||||
| ------- | -------- | ----------- | ------------ | ----------- | -------------- | --------------- | --------------- | ------------------ | ------------------- | -------- | ----------- | ------------ |
|
||||
| FuelSafe | Hold | Flow | Flow | Hold | Hold | Hold | Hold | Warm | Ignite | Hold | Warm | Ignite |
|
||||
| FuelCaution | | Hold | Flow | Hold | Hold | Hold | Warm | Ignite | Ignite | Warm | Ignite | Ignite |
|
||||
| FuelCritical | | | Hold | Hold | Hold | Hold | Ignite | Ignite | Ignite | Ignite | Ignite | Ignite |
|
||||
| CoolantSafe | | | | Hold | Flow | Flow | Hold | Short | Short | Hold | Quench | Quench |
|
||||
| CoolantCaution | | | | | Hold | Flow | Short | Short | Short | Hold | Quench | Quench |
|
||||
| CoolantCritical | | | | | | Hold | Short | Short | Short | Hold | Quench | Quench |
|
||||
| FuelSafe | Hold | Flow | Flow | Hold | Dilute | Dilute | Hold | Warm | Ignite | Hold | Warm | Ignite |
|
||||
| FuelCaution | | Hold | Flow | Dilute | Dilute | Dilute | Warm | Ignite | Ignite | Warm | Ignite | Ignite |
|
||||
| FuelCritical | | | Hold | Dilute | Dilute | Dilute | Ignite | Ignite | Ignite | Ignite | Ignite | Ignite |
|
||||
| CoolantSafe | | | | Hold | Flow | Flow | Hold | Conduct | Conduct | Hold | Quench | Quench |
|
||||
| CoolantCaution | | | | | Hold | Flow | Conduct | Conduct | Conduct | Quench | Quench | Quench |
|
||||
| CoolantCritical | | | | | | Hold | Conduct | Conduct | Conduct | Quench | Quench | Quench |
|
||||
| ElectricitySafe | | | | | | | Hold | Flow | Flow | Hold | Hold | Hold |
|
||||
| ElectricityCaution | | | | | | | | Hold | Flow | Hold | Hold | Hold |
|
||||
| ElectricityCritical | | | | | | | | | Hold | Hold | Hold | Hold |
|
||||
@@ -318,17 +446,28 @@ The pair table maps projected bands to parameterized verbs:
|
||||
| HeatCaution | | | | | | | | | | | Hold | Flow |
|
||||
| HeatCritical | | | | | | | | | | | | Hold |
|
||||
|
||||
Blank lower-triangle entries mirror the corresponding upper-triangle entry. Fuel and coolant do not directly react with each other; leaked coolant plus leaked fuel is `Hold` unless another pair on that cell or an adjacent cell involves heat, electricity, or same-carrier flow.
|
||||
Blank lower-triangle entries mirror the corresponding upper-triangle entry.
|
||||
|
||||
Design rules:
|
||||
|
||||
- fuel becomes dangerous through electricity or heat,
|
||||
- coolant becomes dangerous through electricity,
|
||||
- coolant opposes heat,
|
||||
- coolant is a sprinkler suppression system,
|
||||
- coolant dilutes fuel before ignition is checked,
|
||||
- coolant quenches heat and never directly increases heat,
|
||||
- coolant becomes dangerous when electricity reaches wet cells,
|
||||
- wet cells conduct electricity faster than dry cells,
|
||||
- sprinkler water evaporates over time, with hot cells evaporating faster than cold cells,
|
||||
- heat equalizes between neighboring floor cells,
|
||||
- same-carrier leaked surface amounts equalize between neighboring floor cells,
|
||||
- doors and remedy blocks gate local interactions.
|
||||
|
||||
Evaporation is value-based, not a fixed-duration wetness timer:
|
||||
|
||||
1. Every step computes evaporation from `AmbientEvaporationPerStep + (surface heat * HeatEvaporationScale)`.
|
||||
2. Evaporation is capped by the current sprinkler water amount on the cell.
|
||||
3. Evaporated sprinkler water reduces heat by `evaporated amount * EvaporationCoolingScale`, clamped at zero heat.
|
||||
4. Heat-driven evaporation can remove wetness quickly from hot cells, while ambient evaporation slowly clears cold wet cells.
|
||||
|
||||
## Structural Integrity
|
||||
|
||||
Structural integrity is resolved after network propagation and before leak injection. It is deterministic and uses balancing values:
|
||||
@@ -340,14 +479,14 @@ Structural integrity is resolved after network propagation and before leak injec
|
||||
|
||||
For every present underground cell:
|
||||
|
||||
1. If the cell is already max integrity, high intensity does not weaken it during that step.
|
||||
1. If the cell is already max integrity, high intensity does not weaken it during that pulse.
|
||||
2. If integrity is below max and intensity is greater than the high threshold, integrity is reduced by `(intensity - threshold) * StructuralIntegrityDamageScale`.
|
||||
3. Integrity is clamped to the 0-10 range.
|
||||
4. If the final integrity is at or below the leak threshold and intensity is positive, the cell becomes `Leaking` and a `LeakState` is created if one does not already exist.
|
||||
|
||||
Automatic leak access follows the same rules as authored leaks:
|
||||
|
||||
- fuel and coolant leaks use the underground cell as their floor access and can only auto-start under floor cells,
|
||||
- fuel leaks and coolant pipe failures use the underground cell as their floor access and can only auto-start under floor cells,
|
||||
- electricity leaks require one adjacent floor access face; if multiple valid faces exist, the deterministic order is north, east, south, west,
|
||||
- if no valid floor access exists, the weakened cell remains damaged but no reachable leak state is created.
|
||||
|
||||
@@ -357,41 +496,49 @@ Repairing a leak sets the underground cell to `Intact`, sets structural integrit
|
||||
|
||||
Data-driven rule predicates and effects are not part of level data. Effects happen through fixed systems:
|
||||
|
||||
- player-issued lengthy interactions toggle props, cycle junctions, use inventory, open or close doors, repair leaks, and activate reactors,
|
||||
- player-issued lengthy interactions toggle props, cycle junctions, use inventory, open or close powered doors, repair leaks, activate terminals, and activate reactors,
|
||||
- network propagation clears transient amount and intensity, then recomputes flow from enabled sources,
|
||||
- consumer resolution derives per-carrier service states from present underground layers,
|
||||
- structural integrity resolution weakens damaged high-pressure cells and creates leaks from low-integrity positive-pressure cells,
|
||||
- leak injection adds carrier hazards to valid floor access cells,
|
||||
- leak injection and sprinkler discharge add carrier hazards or sprinkler water to valid floor access cells,
|
||||
- surface interaction resolution spreads and reacts hazards according to the hazard pair table,
|
||||
- robot safety resolves terminal loss from unsafe final hazard states after surface interactions,
|
||||
- robot safety resolves `UnsafeEntryLoss` when `MoveRobot` enters an `Unsafe` destination floor cell,
|
||||
- reactor state derives readiness or terminal heat loss,
|
||||
- duration advancement reduces remedy blocks and heat immunity counters,
|
||||
- forecast refresh simulates copied state over the configured horizon.
|
||||
- forecast refresh simulates copied state over the configured pulse horizon for active and powered `AllSeeingEyeTerminal` viewing.
|
||||
|
||||
Warnings are generated by fixed forecast and status systems when conditions can be proven.
|
||||
|
||||
## Simulation Order
|
||||
## Pulse And Step Order
|
||||
|
||||
One lengthy interaction resolves in this order:
|
||||
One accepted lengthy interaction resolves one pulse in this order:
|
||||
|
||||
1. Apply the accepted player mutation.
|
||||
2. Validate runtime state.
|
||||
3. Propagate underground networks.
|
||||
4. Resolve consumers and service production.
|
||||
5. Resolve structural integrity and automatic leak creation.
|
||||
6. Inject leaks.
|
||||
7. Evaluate same-cell surface interactions.
|
||||
8. Evaluate adjacent floor interactions across unblocked door cells.
|
||||
9. Accumulate and apply deltas in deterministic priority order.
|
||||
10. Clamp values.
|
||||
11. Resolve robot safety.
|
||||
12. Derive reactor readiness and level state.
|
||||
13. Advance remedy blocks and heat immunity.
|
||||
14. Refresh forecasts.
|
||||
6. Resolve the configured number of deterministic steps.
|
||||
7. Derive reactor readiness and level state.
|
||||
8. Preserve robot position; a `Pulse` does not cause `UnsafeEntryLoss` by itself.
|
||||
9. Advance remedy blocks and heat immunity once for the pulse.
|
||||
10. Refresh forecasts for active and powered `AllSeeingEyeTerminal` viewing.
|
||||
|
||||
Each step inside the pulse resolves in this order:
|
||||
|
||||
1. Inject leaks and sprinkler discharge fractionally for this step.
|
||||
2. Resolve coolant mitigation against fuel and heat.
|
||||
3. Resolve evaporation and latent cooling.
|
||||
4. Evaluate same-cell surface interactions, including ignition and electrical conduction.
|
||||
5. Evaluate adjacent floor interactions across unblocked door cells.
|
||||
6. Accumulate and apply deltas in deterministic priority order.
|
||||
7. Clamp values.
|
||||
|
||||
## Forecasts
|
||||
|
||||
Forecasts are deterministic simulations over copied state. Forecasting does not mutate the actual level.
|
||||
`Forecast` output is generated systemically by deterministic simulations over copied state. Forecasting does not mutate the actual level. Level data does not author bespoke forecast text.
|
||||
|
||||
`Forecast` output is visible only while the robot is at an active and powered `AllSeeingEyeTerminal`. Away from an active and powered terminal, the player relies on visible props, visible leaks, visible surface values, consumer state, and level state.
|
||||
|
||||
Forecast output includes:
|
||||
|
||||
@@ -399,9 +546,12 @@ Forecast output includes:
|
||||
- reactor ready forecasts,
|
||||
- starved required consumer warnings,
|
||||
- growing hazard warnings when values cross caution or critical bands,
|
||||
- wet-electricity spread warnings,
|
||||
- isolation valve warnings for branch shutoff, restored pressure, isolated leaks, and downstream service loss,
|
||||
- coolant pressure-drop warnings from sprinkler valve use,
|
||||
- structural integrity leak warnings when weakened cells are expected to leak.
|
||||
|
||||
The forecast horizon is balance data.
|
||||
Forecast timing is reported in pulses, for example `Pulse +2`. The forecast horizon is balance data.
|
||||
|
||||
## Validation
|
||||
|
||||
@@ -414,8 +564,11 @@ Validation errors:
|
||||
- wall cell with surface hazards,
|
||||
- prop on invalid terrain,
|
||||
- invalid required consumer counts,
|
||||
- invalid door cell,
|
||||
- invalid door cell or door without local electricity,
|
||||
- invalid leak access,
|
||||
- invalid wall-mounted `CoolantSprinklerValve` outlet/access face or missing coolant network connection,
|
||||
- invalid or missing `SprinklerControlProp` link for a `CoolantSprinklerValve`,
|
||||
- isolation valve without exactly one matching underground carrier,
|
||||
- junction without exactly one underground carrier,
|
||||
- ambiguous junction flow,
|
||||
- network loop or equal-source ambiguity at a junction,
|
||||
@@ -427,7 +580,9 @@ Validation warnings:
|
||||
- underground cell with no source path,
|
||||
- initially starved required consumer,
|
||||
- initially unready reactor,
|
||||
- isolation valve with no meaningful upstream/downstream distinction,
|
||||
- unused remedy supply,
|
||||
- sprinkler valve with no useful suppression target,
|
||||
- visible hazard with no detectable nearby remedy or route.
|
||||
|
||||
## Editor And Schema
|
||||
@@ -437,14 +592,17 @@ The editor authors:
|
||||
- surface terrain,
|
||||
- underground fuel, coolant, and electricity cells,
|
||||
- flow props,
|
||||
- isolation valves,
|
||||
- multi-service consumer props,
|
||||
- required fuel, coolant, and electricity consumer counts,
|
||||
- junction props and balance-defined ratio mode index,
|
||||
- door props,
|
||||
- all-seeing-eye terminals,
|
||||
- electric door props,
|
||||
- wall-mounted coolant sprinkler valves and their authored outlet/access floor cells,
|
||||
- sprinkler control props and their linked coolant sprinkler valves,
|
||||
- `AllSeeingEyeTerminal` props,
|
||||
- remedy supplies,
|
||||
- floor leaks and electricity wall leaks with authored access faces,
|
||||
- initial surface hazards and heat,
|
||||
- initial surface hazards, sprinkler water, and heat,
|
||||
- robot start position.
|
||||
|
||||
The editor includes layer selection for Surface, Electricity, Fuel, and Coolant:
|
||||
@@ -455,12 +613,14 @@ The editor includes layer selection for Surface, Electricity, Fuel, and Coolant:
|
||||
- Networks render as thick lines connecting adjacent cell centers; sources render as large centered dots.
|
||||
- Tools are layer-aware. Cursor is always available. Surface terrain, props, consumers, hazards, doors, and heat tools are available only on Surface. Network painting and sources are available only on their matching underground layer.
|
||||
|
||||
Editor tool badges and drag previews use stable semantic image keys when assets are available. Assets may be added under `Images/Badges` or `Images/Elements` with filenames such as `tool-door.png`, `prop-reactor.png`, `carrier-fuel-source.png`, `leak-electricity.png`, or `robot.png`; missing assets fall back to compact procedural badges and text labels.
|
||||
|
||||
The serialized level schema stores level metadata, dimensions, terrain, underground layers including structural integrity, props and prop state, required reactor consumer counts, leaks, robot state, inventory, forecasts, and dynamic state when saving active play.
|
||||
|
||||
The loader accepts only schema-valid level data and returns clear errors for malformed data.
|
||||
|
||||
## Balancing And Tests
|
||||
|
||||
Balancing defines source strengths, falloff, ratio math, consumer predicates, leak magnitudes, structural integrity thresholds and damage scale, interaction magnitudes, display thresholds, robot safety thresholds, terminal heat thresholds, inventory capacity, remedy duration, heat immunity duration, and forecast horizon.
|
||||
Balancing defines source strengths, falloff, ratio math, isolation valve branch blocking, consumer predicates, leak magnitudes, sprinkler discharge, coolant pressure drops, evaporation, wet-electricity conduction, structural integrity thresholds and damage scale, interaction magnitudes, display thresholds, `Unsafe` thresholds, terminal heat thresholds, inventory capacity, remedy duration, heat immunity duration, and forecast horizon.
|
||||
|
||||
Tests assert behavior against configured balance values and bands. Coverage includes validation, inferred connectivity, junction effects, multi-service consumer states, reactor readiness and activation, terminal loss, robot hazard loss, heat immunity, structural integrity degradation and leak creation, leak access, remedies, door blocking, forecasts, and serialization round trips.
|
||||
Tests assert behavior against configured balance values and bands. Coverage includes validation, inferred connectivity, isolation valve branch blocking, junction effects, multi-service consumer states, reactor readiness and activation, terminal loss, unsafe entry loss, heat immunity, structural integrity degradation and leak creation, pressure-fed leak injection, leak access, wall-mounted sprinkler valves, sprinkler control links, coolant pressure drops, coolant mitigation, evaporation, wet-electricity spread, remedies, powered door toggling and blocking, powered terminal access, unpowered `PoweredProp` no-op pulses, terminal-local forecasts, campaign authoring rules, and serialization round trips.
|
||||
|
||||
BIN
output/imagegen/godot-mood/01-splash-screen.png
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
BIN
output/imagegen/godot-mood/02-main-menu.png
Normal file
|
After Width: | Height: | Size: 2.3 MiB |
BIN
output/imagegen/godot-mood/03-campaign-intro.png
Normal file
|
After Width: | Height: | Size: 2.3 MiB |
BIN
output/imagegen/godot-mood/04-generation-screen-random-level.png
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
BIN
output/imagegen/godot-mood/05-options-screen.png
Normal file
|
After Width: | Height: | Size: 1.7 MiB |
BIN
output/imagegen/godot-mood/06-tutorial-screen.png
Normal file
|
After Width: | Height: | Size: 1.8 MiB |
BIN
output/imagegen/godot-mood/07-game-over-screen.png
Normal file
|
After Width: | Height: | Size: 2.3 MiB |
BIN
output/imagegen/godot-mood/08-game-won-screen.png
Normal file
|
After Width: | Height: | Size: 2.0 MiB |
BIN
output/imagegen/godot-mood/09-lose-overlay.png
Normal file
|
After Width: | Height: | Size: 2.0 MiB |
BIN
output/imagegen/godot-mood/10-win-overlay.png
Normal file
|
After Width: | Height: | Size: 1.6 MiB |
BIN
output/imagegen/godot-mood/11-reusable-controls-kit.png
Normal file
|
After Width: | Height: | Size: 1.8 MiB |
BIN
output/imagegen/godot-mood/12-level-surface-standard.png
Normal file
|
After Width: | Height: | Size: 3.0 MiB |
BIN
output/imagegen/godot-mood/13-level-surface-critical.png
Normal file
|
After Width: | Height: | Size: 2.8 MiB |
BIN
output/imagegen/godot-mood/14-level-underground-network.png
Normal file
|
After Width: | Height: | Size: 3.4 MiB |
|
After Width: | Height: | Size: 3.1 MiB |
|
After Width: | Height: | Size: 3.6 MiB |
BIN
output/imagegen/godot-mood/17-all-seeing-eye-overlay-detail.png
Normal file
|
After Width: | Height: | Size: 2.4 MiB |
BIN
output/imagegen/godot-mood/18-prop-sheet-machinery.png
Normal file
|
After Width: | Height: | Size: 2.6 MiB |
BIN
output/imagegen/godot-mood/19-hazard-and-failure-sheet.png
Normal file
|
After Width: | Height: | Size: 2.9 MiB |
BIN
output/imagegen/godot-mood/20-robot-design-sheet.png
Normal file
|
After Width: | Height: | Size: 1.7 MiB |
BIN
output/imagegen/godot-mood/21-tile-floor-wall-pipe-kit.png
Normal file
|
After Width: | Height: | Size: 3.3 MiB |
BIN
output/imagegen/godot-mood/22-level-lore-and-campaign-map.png
Normal file
|
After Width: | Height: | Size: 2.3 MiB |
22
output/imagegen/godot-mood/prompts.jsonl
Normal file
@@ -0,0 +1,22 @@
|
||||
{"out":"01-splash-screen.png","use_case":"stylized-concept","size":"1536x1024","quality":"medium","prompt":"Create a mood image for the Godot SplashScreen of Reactor Maintenance. Use the art bible: modern elegant stylized playful sci-fi, scrappy industrial reactor-maintenance tone, readable top-down 2D game art language, crisp simplified shapes, selective dark outlines, restrained lighting, cool graphite and blue-gray steel base with semantic accents, texture through hue variation rather than luminance noise. Scene: a short branded entry screen, quiet reactor control bay floor plates seen from top-down/isometric-flat 2D, compact maintenance robot silhouette near a dormant reactor emblem, one calm pulsing green-white status light. Composition: centered title-space mood without legible title text, clean negative space, first-screen polish. Avoid photorealism, noisy texture, tiny mechanical clutter, heavy bloom, CRT, cyberpunk neon overload, text, watermark."}
|
||||
{"out":"02-main-menu.png","use_case":"ui-mockup","size":"1536x1024","quality":"medium","prompt":"Create a mood image for the Godot MainMenu of Reactor Maintenance. Apply the full art bible: readable stylized 2D sci-fi, scrappy industrial, crisp simplified panels, selective dark outlines, cool graphite steel, hue-varied worn paint, semantic accents only. Screen concept: elegant main menu over a top-down reactor maintenance floor, vertical command stack area suggested with clean button silhouettes but no readable text, compact reactor core icon motif, muted pipe routes in background, playful but functional. Composition: 16:9 game UI mockup, restrained panels, readable hierarchy, menu commands prominent. Avoid text, watermark, photorealism, noisy grime, tiny wires everywhere, heavy glow."}
|
||||
{"out":"03-campaign-intro.png","use_case":"ui-mockup","size":"1536x1024","quality":"medium","prompt":"Create a mood image for CampaignIntro scene in Reactor Maintenance. Use the art bible style: modern elegant stylized playful sci-fi, scrappy industrial reactor-maintenance, clean silhouettes, top-down tactical asset language, cool steel and graphite with restrained teal and amber accents, hue variation for worn panels. Screen: level briefing panel with level name/flavor text areas represented as abstract unreadable blocks, small handcrafted level map thumbnail, worn maintenance docket feel without paper fantasy, Begin/Back button silhouettes. Mood: lore-rich but compact, industrial and approachable. Avoid readable text, watermark, photorealism, dense clutter, high-frequency scratches."}
|
||||
{"out":"04-generation-screen-random-level.png","use_case":"ui-mockup","size":"1536x1024","quality":"medium","prompt":"Create a mood image for the GenerationScreen / Play Random Level loading screen of Reactor Maintenance. Art bible: crisp stylized 2D, scrappy industrial sci-fi, selective dark outlines, cool graphite/blue-gray steel, semantic red/cyan/yellow/orange/green accents, hue-based texture only. Screen: procedural level assembly mood, modular tiles and thick pipe route fragments sliding into a grid, small maintenance robot waiting at edge, compact progress panel with abstract unreadable glyph rows. Composition: clear center grid construction, minimal animation implication, not decorative. Avoid readable text, watermark, noisy texture, neon cyberpunk, heavy bloom."}
|
||||
{"out":"05-options-screen.png","use_case":"ui-mockup","size":"1536x1024","quality":"medium","prompt":"Create a mood image for the OptionsScreen of Reactor Maintenance. Follow art bible: modern elegant stylized playful sci-fi with scrappy industrial reactor-maintenance tone, crisp simplified UI panels, cool steel/graphite palette, hue variation not noise, selective outlines. Screen: utilitarian settings panel with audio sliders, fullscreen toggle, UI scale selector, accessibility toggles, and Back button represented visually without readable text. Style: quiet operational UI, dense but organized, no marketing hero, no nested decorative cards. Avoid text, watermark, photorealism, noisy grime, tiny details."}
|
||||
{"out":"06-tutorial-screen.png","use_case":"ui-mockup","size":"1536x1024","quality":"medium","prompt":"Create a mood image for TutorialScreen in Reactor Maintenance. Use the art bible style: readable stylized 2D, modern elegant playful sci-fi, scrappy industrial, restrained lighting, graphite/steel base, semantic accents, hue-varied wear, clean silhouettes. Screen: tutorial topic list beside a content panel, small top-down grid vignette demonstrating robot movement, hazard remedy, forecast, and reactor activation through icons and abstract diagrams, no readable text. Composition: teach interaction grammar clearly, practical UI for mouse/keyboard. Avoid text, watermark, photorealism, clutter, tiny unreadable icon noise."}
|
||||
{"out":"07-game-over-screen.png","use_case":"ui-mockup","size":"1536x1024","quality":"medium","prompt":"Create a mood image for GameOverScreen in Reactor Maintenance. Art bible: stylized top-down sci-fi, scrappy industrial, clean silhouettes, cool graphite/blue-gray steel, selective dark outlines, hue variation over luminance noise. Screen: full-screen campaign failure endpoint, darkened reactor floor, one failed level map silhouette, restrained red critical accents and orange heat support, retry and main menu button shapes without readable text. Mood: tense but not horror, readable and elegant. Avoid gore, photorealism, heavy smoke, noisy texture, text, watermark."}
|
||||
{"out":"08-game-won-screen.png","use_case":"ui-mockup","size":"1536x1024","quality":"medium","prompt":"Create a mood image for GameWonScreen in Reactor Maintenance. Use art bible: modern elegant stylized playable sci-fi, scrappy industrial but hopeful, crisp 2D shapes, selective outlines, cool steel/graphite base, green-white ready/won accents, hue-shifted worn metal. Screen: campaign completion endpoint, reactor core safely online, repaired pipe networks softly organized, final lore panel and completed-count area represented as abstract unreadable blocks, single main menu button shape. Avoid text, watermark, photorealism, noisy texture, excessive glow."}
|
||||
{"out":"09-lose-overlay.png","use_case":"ui-mockup","size":"1536x1024","quality":"medium","prompt":"Create a mood image for LoseOverlay in Reactor Maintenance. Apply art bible: crisp stylized 2D, scrappy industrial reactor UI, cool steel graphite palette, semantic critical red/orange accents, selective outlines, hue variation not noisy luminance. Screen: level grid visible dimmed behind immediate failure modal, modal has cause indicator area, Retry Level and Main Menu button silhouettes, clear focus, paused grid input. Mood: urgent but clean, no horror. Avoid readable text, watermark, photorealism, dense debris, heavy bloom."}
|
||||
{"out":"10-win-overlay.png","use_case":"ui-mockup","size":"1536x1024","quality":"medium","prompt":"Create a mood image for WinOverlay in Reactor Maintenance. Art bible: modern elegant stylized sci-fi, scrappy industrial but repaired, crisp simplified UI, selective dark outlines, cool steel/graphite with bright green-white ready accents. Screen: level grid dimmed behind success modal, reactor online state, next level / finish campaign / main menu button silhouettes without readable text, compact completion text area represented abstractly. Mood: satisfying, functional, not celebratory fireworks. Avoid text, watermark, photorealism, excessive glow, noisy texture."}
|
||||
{"out":"11-reusable-controls-kit.png","use_case":"ui-mockup","size":"1536x1024","quality":"medium","prompt":"Create a mood board image for reusable Godot controls in Reactor Maintenance: PrimaryButton, StateBadge, LevelHeader, CellInspector, ForecastList, InventoryStrip, OutcomeOverlay, ConfirmDialog. Use art bible: stylized readable 2D sci-fi, scrappy industrial, crisp simplified panels, selective outlines, cool graphite and blue-gray steel with semantic red/cyan/yellow/orange/green accents, hue variation instead of noisy texture. Composition: organized UI component sheet with visual examples but no readable text, button states, badges, inventory icons, forecast rows. Avoid text labels, watermark, photorealism, high-frequency detail."}
|
||||
{"out":"12-level-surface-standard.png","use_case":"stylized-concept","size":"2048x1152","quality":"medium","prompt":"Create a detailed mood image for LevelScreen surface gameplay in Reactor Maintenance. Use the art bible exactly: readable top-down tactical 2D game art, modern elegant stylized playful sci-fi, scrappy industrial reactor-maintenance, crisp simplified shapes, selective dark outlines, cool graphite/blue-gray steel base, semantic fuel red/coolant cyan/electric yellow/heat orange/ready green accents, hue-variation texture not luminance noise. Scene: full Godot LevelScreen layout with top header, central grid viewport, right inspector panel, bottom action bar, inventory strip. Grid shows floor plates, walls, thick pipes, robot marker, control terminals, cooling pump, generator, pressure regulator, reactor control. UI has abstract blocks/icons, no readable text. Composition: playable, readable, dense but organized. Avoid photorealism, noisy grime, tiny wires, text, watermark."}
|
||||
{"out":"13-level-surface-critical.png","use_case":"stylized-concept","size":"2048x1152","quality":"medium","prompt":"Create a mood image for a critical LevelScreen moment in Reactor Maintenance. Art bible: readable top-down stylized 2D, scrappy industrial sci-fi, clear silhouettes, selective outlines, cool graphite steel, hue-based wear, semantic accents. Scene: same Godot LevelScreen layout with grid, inspector, action bar. Gameplay state: reactor near critical, fuel slick red pooling, coolant spill cyan, electric arcs yellow, heat patches orange-white, robot positioned near a valve and terminal, forecast warnings in side panel as icon rows, Activate Reactor command visually prominent but other actions present. Keep hazards immediately distinct and large-shaped, not noisy. Avoid readable text, watermark, photorealism, heavy bloom, chaotic debris."}
|
||||
{"out":"14-level-underground-network.png","use_case":"stylized-concept","size":"2048x1152","quality":"medium","prompt":"Create a mood image for the underground network view of Reactor Maintenance LevelScreen. Use art bible: crisp stylized top-down 2D, scrappy industrial reactor maintenance, cool graphite/steel, semantic route colors, hue variation not noise, selective dark outlines. Scene: beneath-floor pipe and conduit layer shown as readable thick continuous routes: fuel pipes with red bands, coolant pipes with cyan-blue bands, electricity conduits with yellow insulated housings. Include junctions, valves, leaks, breaks, source nodes, and terminal access points with oversized state lights. Composition: gameplay map view, clear route logic, no tiny pipe bundles. Avoid text, watermark, photorealism, tangled clutter, high-frequency scratches."}
|
||||
{"out":"15-level-surface-underground-split.png","use_case":"stylized-concept","size":"2048x1152","quality":"medium","prompt":"Create a mood image for LevelScreen showing combined surface and underground awareness in Reactor Maintenance. Art bible: modern elegant stylized 2D sci-fi, scrappy industrial, readable top-down tactical, selective outlines, restrained lighting, cool steel/graphite, hue-varied texture, semantic accents. Scene: central grid uses a surface/underground split view: left half normal floor with robot, props and hazards; right half ghosted cutaway under-floor pipe/conduit network, with a terminal creating an all-seeing scan cone. Include inspector panel highlighting both surface hazards and underground values as abstract icon rows. Avoid readable text, watermark, photorealism, clutter, excessive glow."}
|
||||
{"out":"16-all-seeing-eye-terminal-interface.png","use_case":"ui-mockup","size":"2048x1152","quality":"medium","prompt":"Create a mood image for the all-seeing-eye interface in Reactor Maintenance, a Godot LevelScreen terminal mode that reveals underground systems. Use art bible: elegant stylized playful sci-fi, scrappy industrial reactor maintenance, crisp simplified shapes, selective dark outlines, cool graphite/blue-gray steel, muted teal/purple-blue secondary diagnostic accents allowed, hue variation over luminance noise. Screen: top-down grid background with a central diagnostic terminal icon shaped like a practical industrial eye/scanner, radial scan overlay revealing hidden pipes, leaks, pressure values, forecast warnings as icon rows, inspector panel with clean diagnostic readouts represented by abstract glyph blocks. Avoid readable text, watermark, CRT scanlines, glitch effects, cyberpunk neon overload, noisy details."}
|
||||
{"out":"17-all-seeing-eye-overlay-detail.png","use_case":"stylized-concept","size":"1536x1024","quality":"medium","prompt":"Create a close mood image for the all-seeing-eye diagnostic overlay visual language in Reactor Maintenance. Art bible: readable stylized 2D game art, scrappy industrial sci-fi, crisp shapes, functional charm, selective outlines, cool steel/graphite with teal diagnostic light and semantic hazard colors, texture through hue shifts. Subject: a top-down terminal access panel projecting an eye-like scanner overlay onto a grid tile, revealing underground fuel/coolant/electric routes below the surface, with large readable icons and state lights, no readable text. Composition: asset/UI mood reference, focused on overlay treatment and scan aesthetics. Avoid text, watermark, photorealism, heavy bloom, tiny wires, noisy texture."}
|
||||
{"out":"18-prop-sheet-machinery.png","use_case":"stylized-concept","size":"2048x1152","quality":"medium","prompt":"Create a mood sheet of designed gameplay props for Reactor Maintenance. Use art bible: top-down readable 2D, modern elegant stylized playful sci-fi, scrappy industrial, crisp simplified shapes, selective dark outlines, cool graphite/blue-gray steel, hue-variation wear, semantic accents. Include separate top-down props with padding: control terminal with teal/green screen, diagnostic all-seeing-eye terminal with scanner identity, cooling pump with round tank/impeller and cyan accent, generator with coil/flywheel and yellow electricity accent, pressure regulator with gauge and valve, reactor control with largest authority silhouette and green activation core. Each prop has one obvious interaction feature and large state light. No labels or text. Avoid photorealism, noisy grime, tiny clutter, watermark."}
|
||||
{"out":"19-hazard-and-failure-sheet.png","use_case":"stylized-concept","size":"2048x1152","quality":"medium","prompt":"Create a mood sheet of hazards and failure visuals for Reactor Maintenance. Follow art bible: stylized readable 2D, clear silhouettes, selective outlines, semantic colors, hue variation rather than luminance noise, no painterly chaos. Include top-down hazard shapes with padding: red fuel slick with smooth pooling, cyan coolant spill with colder edges, angular yellow electric arcs or charged patch, orange-white heat/fire patch, broken pipe leak, ruptured conduit, critical reactor warning tile. Use large readable shapes and quiet interiors for icons/highlights. Avoid text, watermark, photorealism, gore, random speckles, dense debris, heavy bloom."}
|
||||
{"out":"20-robot-design-sheet.png","use_case":"stylized-concept","size":"1536x1024","quality":"medium","prompt":"Create a mood sheet for the Reactor Maintenance player robot. Art bible: compact practical likable but not comedic, readable top-down body shape, small tool arms or maintenance appendages, visible direction cue, one clear status light, brighter than floor tiles but less saturated than hazards, cool graphite/blue-gray steel body with restrained accent colors, hue-varied worn paint. Include several top-down pose/state variations: idle, moving, interacting with valve, shielded from heat, carrying remedy canister. Crisp stylized 2D, selective outlines, generous padding. Avoid text, watermark, photorealism, noisy texture, tiny mechanical clutter."}
|
||||
{"out":"21-tile-floor-wall-pipe-kit.png","use_case":"stylized-concept","size":"2048x1152","quality":"medium","prompt":"Create a mood sheet for floor, wall, and pipe tile art in Reactor Maintenance. Use art bible: readable top-down tactical 2D, modern elegant stylized scrappy industrial sci-fi, cool graphite/blue-gray steel, subtle hue shifts between plates, selective outlines, texture by broad hue variation not luminance noise. Include quiet floor plates, rubberized seams, maintenance decking, heavy wall blockers with reinforced ribs, warning trim, thick pipe segments, elbows, junctions, valves, source nodes, leaks and breaks for fuel/coolant/electricity. Keep tiles grid-readable and not decorative. Avoid text, watermark, photorealism, noisy scratches, tiny pipe bundles."}
|
||||
{"out":"22-level-lore-and-campaign-map.png","use_case":"ui-mockup","size":"1536x1024","quality":"medium","prompt":"Create a mood image for campaign level selection/lore presentation in Reactor Maintenance, supporting the CampaignIntro and campaign chain concept. Apply art bible: stylized readable 2D sci-fi, scrappy industrial reactor maintenance, crisp simplified UI, cool graphite/steel, restrained semantic accents, hue variation texture. Scene: linear chain of handcrafted level nodes inside a maintenance facility schematic, each node represented by icon-only badges and small abstract flavor panels, current level highlighted, completed levels green-white, dangerous future levels amber/red. No readable text. Avoid watermark, photorealism, noisy clutter, neon overload, heavy glow."}
|
||||
|
After Width: | Height: | Size: 1.4 MiB |
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://g8njonkahvns"
|
||||
path="res://.godot/imported/maintenance_robot.png-77cbb0f8610a91dc82df7b4565ee0a3a.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Assets/Characters/maintenance_robot.png"
|
||||
dest_files=["res://.godot/imported/maintenance_robot.png-77cbb0f8610a91dc82df7b4565ee0a3a.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
BIN
src/ReactorMaintenance.Godot/Assets/Terrain/terrain_tilemap.png
Normal file
|
After Width: | Height: | Size: 3.9 MiB |
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://c8ni4xlm0da87"
|
||||
path="res://.godot/imported/terrain_tilemap.png-c82b5455f5449af21e1ce16efe8292b9.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Assets/Terrain/terrain_tilemap.png"
|
||||
dest_files=["res://.godot/imported/terrain_tilemap.png-c82b5455f5449af21e1ce16efe8292b9.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
BIN
src/ReactorMaintenance.Godot/Assets/Ui/coolant_icon.png
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://c460u833mc8wd"
|
||||
path="res://.godot/imported/coolant_icon.png-5f62705f72d18fbac1995d1563570521.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Assets/Ui/coolant_icon.png"
|
||||
dest_files=["res://.godot/imported/coolant_icon.png-5f62705f72d18fbac1995d1563570521.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
BIN
src/ReactorMaintenance.Godot/Assets/Ui/electric_icon.png
Normal file
|
After Width: | Height: | Size: 1.4 MiB |
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://c15hva5fpyqb0"
|
||||
path="res://.godot/imported/electric_icon.png-f93d44cfb4888519024e3d64de9dbe62.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Assets/Ui/electric_icon.png"
|
||||
dest_files=["res://.godot/imported/electric_icon.png-f93d44cfb4888519024e3d64de9dbe62.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
BIN
src/ReactorMaintenance.Godot/Assets/Ui/fuel_icon.png
Normal file
|
After Width: | Height: | Size: 1.4 MiB |
40
src/ReactorMaintenance.Godot/Assets/Ui/fuel_icon.png.import
Normal file
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://2v0gglovpy7t"
|
||||
path="res://.godot/imported/fuel_icon.png-8e723683b233f30e6235d28f228f5d83.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Assets/Ui/fuel_icon.png"
|
||||
dest_files=["res://.godot/imported/fuel_icon.png-8e723683b233f30e6235d28f228f5d83.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
BIN
src/ReactorMaintenance.Godot/Assets/Ui/heat_shield_icon.png
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://6lpfwf7pkx0c"
|
||||
path="res://.godot/imported/heat_shield_icon.png-80a3ecc066be8caa1d77f8459e73bf57.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Assets/Ui/heat_shield_icon.png"
|
||||
dest_files=["res://.godot/imported/heat_shield_icon.png-80a3ecc066be8caa1d77f8459e73bf57.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
BIN
src/ReactorMaintenance.Godot/Assets/Ui/primary_button_accent.png
Normal file
|
After Width: | Height: | Size: 1.5 MiB |
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bj45xeusdpf2g"
|
||||
path="res://.godot/imported/primary_button_accent.png-99d4e7a7f8a491f0ae2203e71d886e65.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Assets/Ui/primary_button_accent.png"
|
||||
dest_files=["res://.godot/imported/primary_button_accent.png-99d4e7a7f8a491f0ae2203e71d886e65.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
BIN
src/ReactorMaintenance.Godot/Assets/Ui/scanner_eye_icon.png
Normal file
|
After Width: | Height: | Size: 1.5 MiB |
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://c3v3xokega3lk"
|
||||
path="res://.godot/imported/scanner_eye_icon.png-003ae6769bc5ea847f0ea3422bbd05b3.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Assets/Ui/scanner_eye_icon.png"
|
||||
dest_files=["res://.godot/imported/scanner_eye_icon.png-003ae6769bc5ea847f0ea3422bbd05b3.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
BIN
src/ReactorMaintenance.Godot/Assets/Ui/state_badge_frame.png
Normal file
|
After Width: | Height: | Size: 1.4 MiB |
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bmpb1t3muf78i"
|
||||
path="res://.godot/imported/state_badge_frame.png-391c9f73136d3046614e3bac00f56da2.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Assets/Ui/state_badge_frame.png"
|
||||
dest_files=["res://.godot/imported/state_badge_frame.png-391c9f73136d3046614e3bac00f56da2.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
@@ -0,0 +1,8 @@
|
||||
{"out":"res://Assets/Characters/maintenance_robot.png","model":"gpt-image-1.5","source":"output/imagegen/godot-mood/20-robot-design-sheet.png","prompt":"Create one transparent PNG cutout of the compact Reactor Maintenance player robot from the reference sheet. Keep the same modern elegant stylized playful sci-fi art direction: readable top-down 2D game asset, compact practical likable maintenance robot, cool graphite and blue-gray steel body, one cyan status light, selective dark outline, broad hue-varied wear instead of noisy luminance scratches. Center a single robot with generous transparent padding, no floor, no shadow, no text, no watermark, no UI frame. The robot must read clearly at 128px, 64px, and 32px."}
|
||||
{"out":"res://Assets/Ui/primary_button_accent.png","model":"gpt-image-1.5","source":"output/imagegen/godot-mood/11-reusable-controls-kit.png","prompt":"Create one transparent PNG cutout for a Reactor Maintenance primary command button accent plate from the reusable controls reference. It should be a compact sci-fi UI button ornament or left icon plate, not a full button with text. Style: modern elegant stylized playful sci-fi, scrappy industrial, cool graphite and blue-gray steel, cyan edge light, selective dark outline, broad hue-varied wear, readable at 44px height. Center it with transparent padding. No text, no watermark, no background, no shadow."}
|
||||
{"out":"res://Assets/Ui/state_badge_frame.png","model":"gpt-image-1.5","source":"output/imagegen/godot-mood/11-reusable-controls-kit.png","prompt":"Create one transparent PNG cutout for a Reactor Maintenance state badge frame from the reusable controls reference. It should be a small rounded industrial sci-fi badge backing with selective dark outline, cool graphite steel, subtle cyan rim detail, quiet center area for overlaid state text, broad hue variation not noisy scratches. Center with transparent padding. No text, no watermark, no background, no shadow."}
|
||||
{"out":"res://Assets/Ui/fuel_icon.png","model":"gpt-image-1.5","source":"output/imagegen/godot-mood/11-reusable-controls-kit.png","prompt":"Create one transparent PNG cutout UI inventory icon for fuel neutralizer in Reactor Maintenance. Use the reusable controls reference style and art bible: flat-cleaner UI art, red fuel droplet/container pictogram, compact industrial badge silhouette, selective outline, readable at 24px. Center with transparent padding. No text, no watermark, no background, no shadow."}
|
||||
{"out":"res://Assets/Ui/coolant_icon.png","model":"gpt-image-1.5","source":"output/imagegen/godot-mood/11-reusable-controls-kit.png","prompt":"Create one transparent PNG cutout UI inventory icon for coolant neutralizer in Reactor Maintenance. Use the reusable controls reference style and art bible: flat-cleaner UI art, cyan-blue droplet/canister pictogram, compact industrial badge silhouette, selective outline, readable at 24px. Center with transparent padding. No text, no watermark, no background, no shadow."}
|
||||
{"out":"res://Assets/Ui/electric_icon.png","model":"gpt-image-1.5","source":"output/imagegen/godot-mood/11-reusable-controls-kit.png","prompt":"Create one transparent PNG cutout UI inventory icon for electricity neutralizer in Reactor Maintenance. Use the reusable controls reference style and art bible: flat-cleaner UI art, yellow lightning/insulated cell pictogram, compact industrial badge silhouette, selective outline, readable at 24px. Center with transparent padding. No text, no watermark, no background, no shadow."}
|
||||
{"out":"res://Assets/Ui/heat_shield_icon.png","model":"gpt-image-1.5","source":"output/imagegen/godot-mood/11-reusable-controls-kit.png","prompt":"Create one transparent PNG cutout UI inventory icon for heat shield in Reactor Maintenance. Use the reusable controls reference style and art bible: flat-cleaner UI art, green-white shield with restrained orange heat edge, compact industrial badge silhouette, selective outline, readable at 24px. Center with transparent padding. No text, no watermark, no background, no shadow."}
|
||||
{"out":"res://Assets/Ui/scanner_eye_icon.png","model":"gpt-image-1.5","source":"output/imagegen/godot-mood/11-reusable-controls-kit.png","prompt":"Create one transparent PNG cutout UI icon for the all-seeing-eye diagnostic scanner in Reactor Maintenance. Use the reusable controls reference style and art bible: flat-cleaner UI art, practical industrial eye/scanner symbol, teal/cyan diagnostic glow with muted graphite casing, selective outline, readable at 24px and 48px. Center with transparent padding. No text, no watermark, no background, no shadow."}
|
||||
107
src/ReactorMaintenance.Godot/Controls/CellInspector.cs
Normal file
@@ -0,0 +1,107 @@
|
||||
using Godot;
|
||||
using ReactorMaintenance.Simulation;
|
||||
using System.Text;
|
||||
|
||||
namespace ReactorMaintenance.Godot.Controls;
|
||||
|
||||
public partial class CellInspector : PanelContainer
|
||||
{
|
||||
public override void _Ready()
|
||||
{
|
||||
var body = new VBoxContainer();
|
||||
AddChild(body);
|
||||
|
||||
var header = new HBoxContainer();
|
||||
header.AddChild(FrontendAssets.CreateIcon(FrontendAssets.ScannerEyeIcon, new(34, 34)));
|
||||
header.AddChild(new Label {
|
||||
Text = "Inspector",
|
||||
VerticalAlignment = VerticalAlignment.Center
|
||||
});
|
||||
body.AddChild(header);
|
||||
|
||||
body.AddChild(m_Text);
|
||||
m_Text.AutowrapMode = TextServer.AutowrapMode.WordSmart;
|
||||
}
|
||||
|
||||
public void SetCellInfo(LevelState? level, GridPosition? position, bool canSeeUnderground)
|
||||
{
|
||||
var pos = position ?? new(-1, -1);
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine($"Selected Cell: {pos.X},{pos.Y}");
|
||||
if (level is null || position is null || !level.InBounds(pos))
|
||||
{
|
||||
m_Text.Text = sb.ToString();
|
||||
return;
|
||||
}
|
||||
|
||||
var terrain = level.GetTerrain(pos);
|
||||
var prop = level.GetProp(pos);
|
||||
var surface = level.GetSurface(pos);
|
||||
sb.AppendLine($"Terrain: {terrain}");
|
||||
sb.AppendLine($"Prop: {FormatProp(prop)}");
|
||||
if (prop.Type != EPropType.None)
|
||||
sb.AppendLine($"Service: {FormatService(prop)}");
|
||||
sb.AppendLine($"Surface: {FormatHazards(surface.Fuel, surface.Water, surface.Electricity, surface.Heat)}");
|
||||
sb.AppendLine(surface.IsUnsafe() ? "Movement: Unsafe" : "Movement: Safe");
|
||||
|
||||
if (canSeeUnderground)
|
||||
sb.AppendLine($"Underground: {FormatUnderground(level, pos)}");
|
||||
else
|
||||
sb.AppendLine("Underground: terminal access required");
|
||||
m_Text.Text = sb.ToString();
|
||||
}
|
||||
|
||||
private static string FormatProp(PropState prop)
|
||||
{
|
||||
return prop.Type switch {
|
||||
EPropType.Flow => $"{prop.Carrier} Flow {prop.SwitchState}",
|
||||
EPropType.Consumer => $"Consumer {prop.SwitchState}",
|
||||
EPropType.IsolationValve => $"{prop.Carrier} Valve {(prop.IsOpen ? "Open" : "Closed")}",
|
||||
EPropType.SprinklerControl => $"Sprinkler Control {prop.SwitchState}",
|
||||
EPropType.SprinklerValve => $"Sprinkler Valve -> {FormatPosition(prop.OutletPosition)}",
|
||||
EPropType.Door => $"Door {prop.DoorState}",
|
||||
EPropType.AllSeeingEyeTerminal => prop.Active ? "All-Seeing-Eye Active" : "All-Seeing-Eye",
|
||||
EPropType.ReactorControl => $"Reactor Control {prop.ReactorId}",
|
||||
EPropType.RemedySupply => prop.Depleted ? $"{prop.RemedyType} Empty" : $"{prop.RemedyType} Supply",
|
||||
_ => prop.Type.ToString()
|
||||
};
|
||||
}
|
||||
|
||||
private static string FormatService(PropState prop)
|
||||
{
|
||||
if (prop.Type == EPropType.Consumer)
|
||||
return $"fuel {prop.FuelServiceState}, water {prop.WaterServiceState}, electricity {prop.ElectricityServiceState}";
|
||||
|
||||
return prop.ServiceState.ToString();
|
||||
}
|
||||
|
||||
private static string FormatUnderground(LevelState level, GridPosition position)
|
||||
{
|
||||
var parts = new List<string>();
|
||||
foreach (var carrier in Enum.GetValues<ECarrierType>())
|
||||
{
|
||||
var cell = level.GetUnderground(position, carrier);
|
||||
if (cell.IsPresent)
|
||||
parts.Add($"{carrier} {cell.State} {cell.Amount:F1}/{cell.Intensity:F1}");
|
||||
}
|
||||
|
||||
return parts.Count > 0 ? string.Join(", ", parts) : "none";
|
||||
}
|
||||
|
||||
private static string FormatPosition(GridPosition? position)
|
||||
{
|
||||
return position is { } p ? $"{p.X},{p.Y}" : "unlinked";
|
||||
}
|
||||
|
||||
private static string FormatHazards(float fuel, float water, float electricity, float heat)
|
||||
{
|
||||
var parts = new List<string>();
|
||||
if (fuel > 0) parts.Add($"fuel {fuel:F1}");
|
||||
if (water > 0) parts.Add($"water {water:F1}");
|
||||
if (electricity > 0) parts.Add($"electricity {electricity:F1}");
|
||||
if (heat > 0) parts.Add($"heat {heat:F1}");
|
||||
return parts.Count > 0 ? string.Join(", ", parts) : "none";
|
||||
}
|
||||
|
||||
private readonly Label m_Text = new();
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://d0d3kglv6s32m
|
||||
33
src/ReactorMaintenance.Godot/Controls/ConfirmDialog.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using Godot;
|
||||
|
||||
namespace ReactorMaintenance.Godot.Controls;
|
||||
|
||||
public partial class ConfirmDialog : PanelContainer
|
||||
{
|
||||
public event Action? Confirmed;
|
||||
public event Action? Canceled;
|
||||
|
||||
public void Configure(string title, string message, string confirmText = "Confirm")
|
||||
{
|
||||
foreach (var child in GetChildren())
|
||||
child.QueueFree();
|
||||
|
||||
var body = new VBoxContainer();
|
||||
AddChild(body);
|
||||
body.AddChild(new Label { Text = title, HorizontalAlignment = HorizontalAlignment.Center });
|
||||
body.AddChild(new Label { Text = message, AutowrapMode = TextServer.AutowrapMode.WordSmart });
|
||||
|
||||
var actions = new HBoxContainer();
|
||||
body.AddChild(actions);
|
||||
|
||||
var confirm = new PrimaryButton();
|
||||
confirm.Configure(confirmText);
|
||||
confirm.Pressed += () => Confirmed?.Invoke();
|
||||
actions.AddChild(confirm);
|
||||
|
||||
var cancel = new PrimaryButton();
|
||||
cancel.Configure("Cancel");
|
||||
cancel.Pressed += () => Canceled?.Invoke();
|
||||
actions.AddChild(cancel);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://cp5qe2n4nwb32
|
||||
64
src/ReactorMaintenance.Godot/Controls/ForecastList.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
using Godot;
|
||||
using ReactorMaintenance.Simulation;
|
||||
|
||||
namespace ReactorMaintenance.Godot.Controls;
|
||||
|
||||
public partial class ForecastList : PanelContainer
|
||||
{
|
||||
public override void _Ready()
|
||||
{
|
||||
AddChild(m_Items);
|
||||
SetForecasts(Array.Empty<Forecast>());
|
||||
}
|
||||
|
||||
public void SetForecasts(IReadOnlyList<Forecast> forecasts)
|
||||
{
|
||||
foreach (var child in m_Items.GetChildren())
|
||||
child.QueueFree();
|
||||
|
||||
m_Items.AddChild(new Label { Text = "Forecasts" });
|
||||
foreach (var forecast in forecasts)
|
||||
{
|
||||
var label = new Label {
|
||||
Text = FormatForecast(forecast),
|
||||
AutowrapMode = TextServer.AutowrapMode.WordSmart
|
||||
};
|
||||
ApplyForecastColor(label, forecast.Kind);
|
||||
m_Items.AddChild(label);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetUnavailable(string reason)
|
||||
{
|
||||
foreach (var child in m_Items.GetChildren())
|
||||
child.QueueFree();
|
||||
|
||||
m_Items.AddChild(new Label { Text = "Forecasts" });
|
||||
m_Items.AddChild(new Label {
|
||||
Text = reason,
|
||||
AutowrapMode = TextServer.AutowrapMode.WordSmart
|
||||
});
|
||||
}
|
||||
|
||||
private static string FormatForecast(Forecast forecast)
|
||||
{
|
||||
var pos = forecast.Position;
|
||||
var posStr = pos != null ? $" [{pos.X},{pos.Y}]" : "";
|
||||
return $"Pulse +{forecast.Turns}: {forecast.Message}{posStr}";
|
||||
}
|
||||
|
||||
private static void ApplyForecastColor(Label label, EForecastKind kind)
|
||||
{
|
||||
var color = kind switch {
|
||||
EForecastKind.TerminalLoss => new(1.0f, 0.36f, 0.32f),
|
||||
EForecastKind.ConsumerStarved => new(1.0f, 0.6f, 0.2f),
|
||||
EForecastKind.HazardGrowth => new(1.0f, 0.8f, 0.2f),
|
||||
EForecastKind.StructuralIntegrity => new(0.6f, 0.8f, 1.0f),
|
||||
EForecastKind.ReactorReady => new(0.45f, 1.0f, 0.58f),
|
||||
_ => Colors.White
|
||||
};
|
||||
label.AddThemeColorOverride("font_color", color);
|
||||
}
|
||||
|
||||
private readonly VBoxContainer m_Items = new();
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://bswy75n15jxl5
|
||||
32
src/ReactorMaintenance.Godot/Controls/FrontendAssets.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using Godot;
|
||||
|
||||
namespace ReactorMaintenance.Godot.Controls;
|
||||
|
||||
internal static class FrontendAssets
|
||||
{
|
||||
public static TextureRect CreateIcon(string path, Vector2 size)
|
||||
{
|
||||
return new() {
|
||||
Texture = LoadTexture(path),
|
||||
CustomMinimumSize = size,
|
||||
ExpandMode = TextureRect.ExpandModeEnum.FitWidthProportional,
|
||||
StretchMode = TextureRect.StretchModeEnum.KeepAspectCentered,
|
||||
MouseFilter = Control.MouseFilterEnum.Ignore
|
||||
};
|
||||
}
|
||||
|
||||
public static Texture2D? LoadTexture(string path)
|
||||
{
|
||||
return ResourceLoader.Exists(path) ? ResourceLoader.Load<Texture2D>(path) : null;
|
||||
}
|
||||
|
||||
public const string WaterIcon = "res://Assets/Ui/water_icon.png";
|
||||
public const string ElectricIcon = "res://Assets/Ui/electric_icon.png";
|
||||
public const string FuelIcon = "res://Assets/Ui/fuel_icon.png";
|
||||
public const string HeatShieldIcon = "res://Assets/Ui/heat_shield_icon.png";
|
||||
public const string MaintenanceRobot = "res://Assets/Characters/maintenance_robot.png";
|
||||
public const string PrimaryButtonAccent = "res://Assets/Ui/primary_button_accent.png";
|
||||
public const string ScannerEyeIcon = "res://Assets/Ui/scanner_eye_icon.png";
|
||||
public const string StateBadgeFrame = "res://Assets/Ui/state_badge_frame.png";
|
||||
public const string TerrainTilemap = "res://Assets/Terrain/terrain_tilemap.png";
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://bfvrecxfcutol
|
||||
656
src/ReactorMaintenance.Godot/Controls/GridViewport.cs
Normal file
@@ -0,0 +1,656 @@
|
||||
using Godot;
|
||||
using ReactorMaintenance.Simulation;
|
||||
|
||||
namespace ReactorMaintenance.Godot.Controls;
|
||||
|
||||
public partial class GridViewport : Control
|
||||
{
|
||||
private readonly record struct SGridLayout(float CellSize, Vector2 Origin)
|
||||
{
|
||||
public Rect2 CellRect(GridPosition position)
|
||||
{
|
||||
return new(Origin + new Vector2(position.X * CellSize, position.Y * CellSize), new(CellSize, CellSize));
|
||||
}
|
||||
|
||||
public Rect2 DualTileRect(int x, int y)
|
||||
{
|
||||
return new(Origin + new Vector2((x - 0.5f) * CellSize, (y - 0.5f) * CellSize), new(CellSize, CellSize));
|
||||
}
|
||||
|
||||
public Vector2 CellCenter(GridPosition position)
|
||||
{
|
||||
return CellRect(position).GetCenter();
|
||||
}
|
||||
}
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
MouseFilter = MouseFilterEnum.Stop;
|
||||
FocusMode = FocusModeEnum.Click;
|
||||
ClipContents = true;
|
||||
m_TerrainTilemap = FrontendAssets.LoadTexture(FrontendAssets.TerrainTilemap);
|
||||
m_RobotTexture = FrontendAssets.LoadTexture(FrontendAssets.MaintenanceRobot);
|
||||
}
|
||||
|
||||
public override void _Draw()
|
||||
{
|
||||
DrawRect(new(Vector2.Zero, Size), c_BackgroundColor);
|
||||
if (m_LevelState is null)
|
||||
return;
|
||||
|
||||
var layout = GetLayout();
|
||||
DrawTerrain(layout, SurfaceOpacity());
|
||||
DrawUnderground(layout);
|
||||
DrawSurfaceHazards(layout, SurfaceOpacity());
|
||||
DrawUnsafeWarnings(layout, SurfaceOpacity());
|
||||
DrawDoors(layout, SurfaceOpacity());
|
||||
DrawProps(layout, SurfaceOpacity());
|
||||
DrawLeaks(layout, SurfaceOpacity());
|
||||
DrawReachableHints(layout);
|
||||
DrawRobot(layout, SurfaceOpacity());
|
||||
DrawGridOverlays(layout);
|
||||
}
|
||||
|
||||
public override void _GuiInput(InputEvent @event)
|
||||
{
|
||||
if (m_LevelState is null)
|
||||
return;
|
||||
|
||||
if (@event is InputEventMouseMotion motion)
|
||||
{
|
||||
if (m_IsPanning)
|
||||
{
|
||||
PanOffset += motion.Relative;
|
||||
QueueRedraw();
|
||||
return;
|
||||
}
|
||||
|
||||
SetHoveredCell(ScreenToCell(motion.Position));
|
||||
return;
|
||||
}
|
||||
|
||||
if (@event is not InputEventMouseButton mouseButton)
|
||||
return;
|
||||
|
||||
if (mouseButton.ButtonIndex is MouseButton.WheelUp or MouseButton.WheelDown && mouseButton.Pressed)
|
||||
{
|
||||
ZoomAt(mouseButton.Position, mouseButton.ButtonIndex == MouseButton.WheelUp ? 1.12f : 1 / 1.12f);
|
||||
AcceptEvent();
|
||||
return;
|
||||
}
|
||||
|
||||
if (mouseButton.ButtonIndex == MouseButton.Middle)
|
||||
{
|
||||
m_IsPanning = mouseButton.Pressed;
|
||||
AcceptEvent();
|
||||
return;
|
||||
}
|
||||
|
||||
if (mouseButton.ButtonIndex == MouseButton.Left && mouseButton.Pressed)
|
||||
{
|
||||
if (ScreenToCell(mouseButton.Position) is { } cell)
|
||||
{
|
||||
SelectedCell = cell;
|
||||
EmitSignal(SignalName.OnCellSelected, cell);
|
||||
EmitSignal(SignalName.OnGridClicked, cell);
|
||||
QueueRedraw();
|
||||
}
|
||||
|
||||
AcceptEvent();
|
||||
}
|
||||
}
|
||||
|
||||
public void SetLevelState(LevelState levelState)
|
||||
{
|
||||
m_LevelState = levelState;
|
||||
RobotPosition = ToVector(levelState.Robot.Position);
|
||||
if (!IsValidCell(SelectedCell))
|
||||
SelectedCell = RobotPosition;
|
||||
|
||||
QueueRedraw();
|
||||
}
|
||||
|
||||
private void DrawTerrain(SGridLayout layout, float opacity)
|
||||
{
|
||||
if (m_LevelState is null)
|
||||
return;
|
||||
|
||||
for (var y = 0; y <= m_LevelState.Height; y++)
|
||||
{
|
||||
for (var x = 0; x <= m_LevelState.Width; x++)
|
||||
DrawDualTerrainTile(layout.DualTileRect(x, y), GetDualTileFloorMask(x, y), opacity);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawDualTerrainTile(Rect2 rect, int floorMask, float opacity)
|
||||
{
|
||||
if (m_TerrainTilemap is null)
|
||||
{
|
||||
DrawFallbackTerrainTile(rect, floorMask, opacity);
|
||||
return;
|
||||
}
|
||||
|
||||
var wallMask = c_AllCorners ^ floorMask;
|
||||
DrawTextureRectRegion(m_TerrainTilemap, rect, TilemapSourceRect(wallMask), new(1, 1, 1, opacity));
|
||||
}
|
||||
|
||||
private void DrawFallbackTerrainTile(Rect2 rect, int floorMask, float opacity)
|
||||
{
|
||||
var color = floorMask == c_AllCorners ? new(0.13f, 0.16f, 0.17f, opacity) : new Color(0.18f, 0.20f, 0.22f, opacity);
|
||||
DrawRect(rect, color);
|
||||
}
|
||||
|
||||
private void DrawUnderground(SGridLayout layout)
|
||||
{
|
||||
foreach (var carrier in OrderedUndergroundLayers())
|
||||
DrawUndergroundLayer(layout, carrier, CarrierColor(carrier), UndergroundOpacity(carrier));
|
||||
}
|
||||
|
||||
private IEnumerable<ECarrierType> OrderedUndergroundLayers()
|
||||
{
|
||||
var carriers = new[] { ECarrierType.Fuel, ECarrierType.Water, ECarrierType.Electricity }.Where(IsLayerVisible).ToArray();
|
||||
return ActiveUndergroundLayer is { } activeCarrier && carriers.Contains(activeCarrier)
|
||||
? carriers.Where(carrier => carrier != activeCarrier).Append(activeCarrier)
|
||||
: carriers;
|
||||
}
|
||||
|
||||
private void DrawUndergroundLayer(SGridLayout layout, ECarrierType carrier, Color color, float opacity)
|
||||
{
|
||||
if (m_LevelState is null)
|
||||
return;
|
||||
|
||||
var layerColor = WithOpacity(color, opacity);
|
||||
var lineWidth = Math.Max(4, layout.CellSize * 0.16f);
|
||||
var cellDotRadius = Math.Max(2, layout.CellSize * 0.08f);
|
||||
var sourceDotRadius = Math.Max(5, layout.CellSize * 0.22f);
|
||||
foreach (var position in AllPositions())
|
||||
{
|
||||
var cell = m_LevelState.GetUnderground(position, carrier);
|
||||
if (!cell.IsPresent)
|
||||
continue;
|
||||
|
||||
var center = layout.CellCenter(position);
|
||||
DrawNetworkConnection(layout, carrier, position, new(position.X + 1, position.Y), layerColor, lineWidth);
|
||||
DrawNetworkConnection(layout, carrier, position, new(position.X, position.Y + 1), layerColor, lineWidth);
|
||||
DrawCircle(center, cellDotRadius, layerColor);
|
||||
|
||||
if (cell.State == EUndergroundState.Leaking)
|
||||
DrawArc(center, sourceDotRadius * 0.7f, 0, Mathf.Tau, 32, c_LeakColor, Math.Max(2, lineWidth * 0.25f));
|
||||
|
||||
var prop = m_LevelState.GetProp(position);
|
||||
if (prop is { Type: EPropType.Flow } && prop.Carrier == carrier)
|
||||
DrawCircle(center, sourceDotRadius, layerColor);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawNetworkConnection(SGridLayout layout, ECarrierType carrier, GridPosition position, GridPosition neighbor, Color color, float lineWidth)
|
||||
{
|
||||
if (m_LevelState is null || !m_LevelState.InBounds(neighbor) || !m_LevelState.GetUnderground(neighbor, carrier).IsPresent)
|
||||
return;
|
||||
|
||||
DrawLine(layout.CellCenter(position), layout.CellCenter(neighbor), color, lineWidth);
|
||||
}
|
||||
|
||||
private void DrawSurfaceHazards(SGridLayout layout, float opacity)
|
||||
{
|
||||
if (m_LevelState is null)
|
||||
return;
|
||||
|
||||
foreach (var position in AllPositions().Where(m_LevelState.IsFloor))
|
||||
{
|
||||
var surface = m_LevelState.GetSurface(position);
|
||||
var rect = layout.CellRect(position);
|
||||
FillHazard(rect, surface.Fuel, c_FuelColor, 0.08f, opacity, Balancing.Current.FuelCaution, Balancing.Current.FuelCritical);
|
||||
FillHazard(rect, surface.Water, c_WaterColor, 0.18f, opacity, Balancing.Current.WaterCaution, Balancing.Current.WaterCritical);
|
||||
FillHazard(rect, surface.Electricity, c_ElectricityColor, 0.28f, opacity, Balancing.Current.ElectricityCaution, Balancing.Current.ElectricityCritical);
|
||||
FillHazard(rect, surface.Heat, c_HeatColor, 0.34f, opacity, Balancing.Current.HeatCaution, Balancing.Current.HeatCritical);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawUnsafeWarnings(SGridLayout layout, float opacity)
|
||||
{
|
||||
if (m_LevelState is null)
|
||||
return;
|
||||
|
||||
foreach (var position in AllPositions().Where(m_LevelState.IsFloor))
|
||||
{
|
||||
if (!m_LevelState.GetSurface(position).IsUnsafe())
|
||||
continue;
|
||||
|
||||
var rect = Inset(layout.CellRect(position), 0.08f);
|
||||
DrawRect(rect, WithOpacity(c_UnsafeColor, opacity), false, Math.Max(2, layout.CellSize * 0.07f));
|
||||
}
|
||||
}
|
||||
|
||||
private void FillHazard(Rect2 rect, float amount, Color color, float inset, float opacity, float caution, float critical)
|
||||
{
|
||||
var overlayOpacity = SurfaceOverlayOpacity(amount, caution, critical);
|
||||
if (overlayOpacity <= 0)
|
||||
return;
|
||||
|
||||
DrawRect(Inset(rect, inset), WithOpacity(color, overlayOpacity * opacity * 0.68f));
|
||||
}
|
||||
|
||||
private void DrawDoors(SGridLayout layout, float opacity)
|
||||
{
|
||||
if (m_LevelState is null)
|
||||
return;
|
||||
|
||||
foreach (var position in AllPositions())
|
||||
{
|
||||
var prop = m_LevelState.GetProp(position);
|
||||
if (prop.Type != EPropType.Door)
|
||||
continue;
|
||||
|
||||
var rect = layout.CellRect(position);
|
||||
var center = layout.CellCenter(position);
|
||||
var color = WithOpacity(prop.DoorState == EDoorState.Open ? c_ReadyColor : c_LeakColor, opacity);
|
||||
var width = Math.Max(3, layout.CellSize * 0.1f);
|
||||
if (IsWall(new(position.X, position.Y - 1)) && IsWall(new(position.X, position.Y + 1)))
|
||||
DrawLine(new(center.X, rect.Position.Y), new(center.X, rect.End.Y), color, width);
|
||||
else
|
||||
DrawLine(new(rect.Position.X, center.Y), new(rect.End.X, center.Y), color, width);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawProps(SGridLayout layout, float opacity)
|
||||
{
|
||||
if (m_LevelState is null)
|
||||
return;
|
||||
|
||||
foreach (var position in AllPositions())
|
||||
{
|
||||
var prop = m_LevelState.GetProp(position);
|
||||
if (prop.Type == EPropType.None || prop.Type == EPropType.Door)
|
||||
continue;
|
||||
|
||||
DrawPropBadge(Inset(layout.CellRect(position), 0.18f), prop, opacity);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawPropBadge(Rect2 rect, PropState prop, float opacity)
|
||||
{
|
||||
var color = WithOpacity(PropColor(prop), opacity);
|
||||
DrawRect(rect, color);
|
||||
DrawRect(rect, WithOpacity(Colors.White, opacity * 0.4f), false, Math.Max(1, rect.Size.X * 0.04f));
|
||||
DrawString(ThemeDB.FallbackFont, rect.GetCenter() + new Vector2(-rect.Size.X * 0.3f, rect.Size.Y * 0.09f), PropLabel(prop), HorizontalAlignment.Center, rect.Size.X * 0.6f, (int)Math.Max(9, rect.Size.Y * 0.24f), WithOpacity(Colors.White, opacity));
|
||||
|
||||
if (prop.Type is EPropType.Flow or EPropType.Consumer)
|
||||
{
|
||||
var indicatorColor = prop.IsEnabled ? c_ReadyColor : c_DisabledColor;
|
||||
DrawCircle(rect.Position + new Vector2(rect.Size.X * 0.82f, rect.Size.Y * 0.18f), Math.Max(3, rect.Size.X * 0.08f), WithOpacity(indicatorColor, opacity));
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawLeaks(SGridLayout layout, float opacity)
|
||||
{
|
||||
if (m_LevelState is null)
|
||||
return;
|
||||
|
||||
foreach (var leak in m_LevelState.Leaks.Where(leak => !leak.Repaired))
|
||||
{
|
||||
var rect = Inset(layout.CellRect(leak.AccessPosition), 0.12f);
|
||||
DrawArc(rect.GetCenter(), rect.Size.X * 0.32f, 0, Mathf.Tau, 24, WithOpacity(CarrierColor(leak.Carrier), opacity), Math.Max(3, rect.Size.X * 0.08f));
|
||||
DrawLine(rect.Position + new Vector2(rect.Size.X * 0.25f, rect.Size.Y * 0.75f), rect.Position + new Vector2(rect.Size.X * 0.75f, rect.Size.Y * 0.25f), WithOpacity(c_LeakColor, opacity), Math.Max(3, rect.Size.X * 0.07f));
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawReachableHints(SGridLayout layout)
|
||||
{
|
||||
if (m_LevelState is null || !IsValidCell(RobotPosition))
|
||||
return;
|
||||
|
||||
var robot = ToGridPosition(RobotPosition);
|
||||
foreach (var neighbor in robot.Neighbors().Where(m_LevelState.IsFloor))
|
||||
DrawRect(Inset(layout.CellRect(neighbor), 0.29f), c_ReachableColor);
|
||||
}
|
||||
|
||||
private void DrawRobot(SGridLayout layout, float opacity)
|
||||
{
|
||||
if (m_LevelState is null)
|
||||
return;
|
||||
|
||||
var rect = Inset(layout.CellRect(m_LevelState.Robot.Position), 0.04f);
|
||||
if (m_RobotTexture is not null)
|
||||
{
|
||||
DrawTextureRect(m_RobotTexture, rect, false, new(1, 1, 1, opacity));
|
||||
return;
|
||||
}
|
||||
|
||||
DrawRect(rect, WithOpacity(Colors.White, opacity));
|
||||
DrawString(ThemeDB.FallbackFont, rect.GetCenter() + new Vector2(-rect.Size.X * 0.28f, rect.Size.Y * 0.08f), "BOT", HorizontalAlignment.Center, rect.Size.X * 0.56f, (int)Math.Max(10, rect.Size.Y * 0.25f), c_BackgroundColor);
|
||||
}
|
||||
|
||||
private void DrawGridOverlays(SGridLayout layout)
|
||||
{
|
||||
if (m_LevelState is null)
|
||||
return;
|
||||
|
||||
foreach (var position in AllPositions())
|
||||
DrawRect(layout.CellRect(position), c_GridLineColor, false, 1);
|
||||
|
||||
if (IsValidCell(HoveredCell) && HoveredCell != SelectedCell)
|
||||
DrawRect(layout.CellRect(ToGridPosition(HoveredCell)), c_HoverColor, false, 2);
|
||||
|
||||
if (IsValidCell(SelectedCell))
|
||||
DrawRect(layout.CellRect(ToGridPosition(SelectedCell)), c_SelectedColor, false, 3);
|
||||
}
|
||||
|
||||
private int GetDualTileFloorMask(int x, int y)
|
||||
{
|
||||
var mask = 0;
|
||||
if (GetTerrainOrWall(x - 1, y - 1) == ECellTerrain.Floor)
|
||||
mask |= c_TopLeftCorner;
|
||||
|
||||
if (GetTerrainOrWall(x, y - 1) == ECellTerrain.Floor)
|
||||
mask |= c_TopRightCorner;
|
||||
|
||||
if (GetTerrainOrWall(x - 1, y) == ECellTerrain.Floor)
|
||||
mask |= c_BottomLeftCorner;
|
||||
|
||||
if (GetTerrainOrWall(x, y) == ECellTerrain.Floor)
|
||||
mask |= c_BottomRightCorner;
|
||||
|
||||
return mask;
|
||||
}
|
||||
|
||||
private ECellTerrain GetTerrainOrWall(int x, int y)
|
||||
{
|
||||
if (m_LevelState is null)
|
||||
return ECellTerrain.Wall;
|
||||
|
||||
var position = new GridPosition(x, y);
|
||||
return m_LevelState.InBounds(position) ? m_LevelState.GetTerrain(position) : ECellTerrain.Wall;
|
||||
}
|
||||
|
||||
private bool IsWall(GridPosition position)
|
||||
{
|
||||
return m_LevelState is not null && m_LevelState.InBounds(position) && m_LevelState.GetTerrain(position) == ECellTerrain.Wall;
|
||||
}
|
||||
|
||||
private Rect2 TilemapSourceRect(int wallMask)
|
||||
{
|
||||
var tilePosition = wallMask switch {
|
||||
c_BottomLeftCorner => new(0, 0),
|
||||
c_TopRightCorner | c_BottomRightCorner => new(1, 0),
|
||||
c_TopLeftCorner | c_BottomLeftCorner | c_BottomRightCorner => new(2, 0),
|
||||
c_BottomLeftCorner | c_BottomRightCorner => new(3, 0),
|
||||
c_TopLeftCorner | c_BottomRightCorner => new(0, 1),
|
||||
c_BottomLeftCorner | c_TopRightCorner | c_BottomRightCorner => new(1, 1),
|
||||
c_AllCorners => new(2, 1),
|
||||
c_TopLeftCorner | c_BottomLeftCorner | c_TopRightCorner => new(3, 1),
|
||||
c_TopRightCorner => new(0, 2),
|
||||
c_TopLeftCorner | c_TopRightCorner => new(1, 2),
|
||||
c_TopLeftCorner | c_TopRightCorner | c_BottomRightCorner => new(2, 2),
|
||||
c_BottomLeftCorner | c_TopLeftCorner => new(3, 2),
|
||||
0 => new(0, 3),
|
||||
c_BottomRightCorner => new(1, 3),
|
||||
c_BottomLeftCorner | c_TopRightCorner => new(2, 3),
|
||||
c_TopLeftCorner => new(3, 3),
|
||||
_ => Vector2I.Zero
|
||||
};
|
||||
|
||||
return new(tilePosition * c_TilemapTileSize, new Vector2I(c_TilemapTileSize, c_TilemapTileSize));
|
||||
}
|
||||
|
||||
private SGridLayout GetLayout()
|
||||
{
|
||||
if (m_LevelState is null)
|
||||
return new(TileSize * Zoom, Vector2.Zero);
|
||||
|
||||
var cellSize = TileSize * Zoom;
|
||||
var contentSize = new Vector2(m_LevelState.Width * cellSize, m_LevelState.Height * cellSize);
|
||||
var origin = ((Size - contentSize) / 2) + PanOffset;
|
||||
return new(cellSize, origin);
|
||||
}
|
||||
|
||||
private Vector2I? ScreenToCell(Vector2 point)
|
||||
{
|
||||
if (m_LevelState is null)
|
||||
return null;
|
||||
|
||||
var layout = GetLayout();
|
||||
var x = Mathf.FloorToInt((point.X - layout.Origin.X) / layout.CellSize);
|
||||
var y = Mathf.FloorToInt((point.Y - layout.Origin.Y) / layout.CellSize);
|
||||
var cell = new Vector2I(x, y);
|
||||
return IsValidCell(cell) ? cell : null;
|
||||
}
|
||||
|
||||
private void ZoomAt(Vector2 point, float factor)
|
||||
{
|
||||
var oldLayout = GetLayout();
|
||||
var cell = (point - oldLayout.Origin) / oldLayout.CellSize;
|
||||
m_Zoom = Math.Clamp(m_Zoom * factor, c_MinZoom, c_MaxZoom);
|
||||
var newCellSize = TileSize * m_Zoom;
|
||||
var contentSize = m_LevelState is null ? Vector2.Zero : new(m_LevelState.Width * newCellSize, m_LevelState.Height * newCellSize);
|
||||
var centeredOrigin = (Size - contentSize) / 2;
|
||||
m_PanOffset = point - centeredOrigin - (cell * newCellSize);
|
||||
QueueRedraw();
|
||||
}
|
||||
|
||||
private void SetHoveredCell(Vector2I? cell)
|
||||
{
|
||||
var next = cell ?? c_InvalidCell;
|
||||
if (next == HoveredCell)
|
||||
return;
|
||||
|
||||
HoveredCell = next;
|
||||
EmitSignal(SignalName.OnCellHovered, HoveredCell);
|
||||
QueueRedraw();
|
||||
}
|
||||
|
||||
private bool IsLayerVisible(ECarrierType carrier)
|
||||
{
|
||||
return carrier switch {
|
||||
ECarrierType.Fuel => ShowFuelLayer,
|
||||
ECarrierType.Water => ShowWaterLayer,
|
||||
ECarrierType.Electricity => ShowElectricityLayer,
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
|
||||
private float SurfaceOpacity()
|
||||
{
|
||||
return ActiveUndergroundLayer is null ? 1.0f : 0.5f;
|
||||
}
|
||||
|
||||
private float UndergroundOpacity(ECarrierType carrier)
|
||||
{
|
||||
if (ActiveUndergroundLayer is null)
|
||||
return 0.25f;
|
||||
|
||||
return ActiveUndergroundLayer == carrier ? 1.0f : 0.25f;
|
||||
}
|
||||
|
||||
private IEnumerable<GridPosition> AllPositions()
|
||||
{
|
||||
if (m_LevelState is null)
|
||||
yield break;
|
||||
|
||||
for (var y = 0; y < m_LevelState.Height; y++)
|
||||
{
|
||||
for (var x = 0; x < m_LevelState.Width; x++)
|
||||
yield return new(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsValidCell(Vector2I cell)
|
||||
{
|
||||
return m_LevelState is not null && cell.X >= 0 && cell.Y >= 0 && cell.X < m_LevelState.Width && cell.Y < m_LevelState.Height;
|
||||
}
|
||||
|
||||
private static Vector2I ToVector(GridPosition position)
|
||||
{
|
||||
return new(position.X, position.Y);
|
||||
}
|
||||
|
||||
private static GridPosition ToGridPosition(Vector2I cell)
|
||||
{
|
||||
return new(cell.X, cell.Y);
|
||||
}
|
||||
|
||||
private static Rect2 Inset(Rect2 rect, float fraction)
|
||||
{
|
||||
var inset = rect.Size.X * fraction;
|
||||
return new(rect.Position + new Vector2(inset, inset), rect.Size - new Vector2(inset * 2, inset * 2));
|
||||
}
|
||||
|
||||
private static float SurfaceOverlayOpacity(float amount, float caution, float critical)
|
||||
{
|
||||
if (amount < caution)
|
||||
return 0;
|
||||
|
||||
if (amount >= critical)
|
||||
return 0.9f;
|
||||
|
||||
var cautionRange = Math.Max(0.001f, critical - caution);
|
||||
var t = (amount - caution) / cautionRange;
|
||||
return 0.3f + (t * 0.35f);
|
||||
}
|
||||
|
||||
private static Color WithOpacity(Color color, float opacity)
|
||||
{
|
||||
return new(color.R, color.G, color.B, color.A * Math.Clamp(opacity, 0, 1));
|
||||
}
|
||||
|
||||
private static Color CarrierColor(ECarrierType carrier)
|
||||
{
|
||||
return carrier switch {
|
||||
ECarrierType.Fuel => c_FuelColor,
|
||||
ECarrierType.Water => c_WaterColor,
|
||||
ECarrierType.Electricity => c_ElectricityColor,
|
||||
_ => Colors.White
|
||||
};
|
||||
}
|
||||
|
||||
private static Color PropColor(PropState prop)
|
||||
{
|
||||
return prop.Type switch {
|
||||
EPropType.Flow => CarrierColor(prop.Carrier),
|
||||
EPropType.Consumer => new(0.36f, 0.48f, 0.67f),
|
||||
EPropType.Junction => new(0.56f, 0.44f, 0.70f),
|
||||
EPropType.AllSeeingEyeTerminal => new(0.33f, 0.59f, 0.61f),
|
||||
EPropType.IsolationValve => CarrierColor(prop.Carrier),
|
||||
EPropType.SprinklerControl => new(0.21f, 0.59f, 0.73f),
|
||||
EPropType.SprinklerValve => new(0.15f, 0.44f, 0.59f),
|
||||
EPropType.RemedySupply => new(0.30f, 0.57f, 0.34f),
|
||||
EPropType.ReactorControl => new(0.69f, 0.28f, 0.29f),
|
||||
_ => Colors.Gray
|
||||
};
|
||||
}
|
||||
|
||||
private static string PropLabel(PropState prop)
|
||||
{
|
||||
return prop.Type switch {
|
||||
EPropType.Flow => $"{CarrierShort(prop.Carrier)} SRC",
|
||||
EPropType.Consumer => "CON",
|
||||
EPropType.Junction => $"J {prop.JunctionMode}",
|
||||
EPropType.AllSeeingEyeTerminal => "EYE",
|
||||
EPropType.IsolationValve => prop.IsOpen ? "V OPEN" : "V CLOSED",
|
||||
EPropType.SprinklerControl => prop.IsEnabled ? "SPR ON" : "SPR OFF",
|
||||
EPropType.SprinklerValve => "SPR",
|
||||
EPropType.RemedySupply => RemedyShort(prop.RemedyType),
|
||||
EPropType.ReactorControl => "REACT",
|
||||
_ => string.Empty
|
||||
};
|
||||
}
|
||||
|
||||
private static string CarrierShort(ECarrierType carrier)
|
||||
{
|
||||
return carrier switch {
|
||||
ECarrierType.Fuel => "F",
|
||||
ECarrierType.Water => "C",
|
||||
ECarrierType.Electricity => "E",
|
||||
_ => "?"
|
||||
};
|
||||
}
|
||||
|
||||
private static string RemedyShort(ERemedyType remedy)
|
||||
{
|
||||
return remedy switch {
|
||||
ERemedyType.FuelNeutralizer => "F REM",
|
||||
ERemedyType.WaterNeutralizer => "C REM",
|
||||
ERemedyType.ElectricityNeutralizer => "E REM",
|
||||
ERemedyType.HeatShield => "H SHD",
|
||||
_ => "REM"
|
||||
};
|
||||
}
|
||||
|
||||
public int TileSize
|
||||
{
|
||||
get => m_TileSize;
|
||||
set
|
||||
{
|
||||
m_TileSize = Math.Max(12, value);
|
||||
QueueRedraw();
|
||||
}
|
||||
}
|
||||
|
||||
public float Zoom
|
||||
{
|
||||
get => m_Zoom;
|
||||
set
|
||||
{
|
||||
m_Zoom = Math.Clamp(value, c_MinZoom, c_MaxZoom);
|
||||
QueueRedraw();
|
||||
}
|
||||
}
|
||||
|
||||
public Vector2 PanOffset
|
||||
{
|
||||
get => m_PanOffset;
|
||||
set
|
||||
{
|
||||
m_PanOffset = value;
|
||||
QueueRedraw();
|
||||
}
|
||||
}
|
||||
|
||||
public Vector2I SelectedCell { get; set; } = c_InvalidCell;
|
||||
public Vector2I HoveredCell { get; private set; } = c_InvalidCell;
|
||||
public Vector2I RobotPosition { get; private set; } = c_InvalidCell;
|
||||
public bool ShowFuelLayer { get; set; } = true;
|
||||
public bool ShowWaterLayer { get; set; } = true;
|
||||
public bool ShowElectricityLayer { get; set; } = true;
|
||||
public ECarrierType? ActiveUndergroundLayer { get; set; }
|
||||
|
||||
[Signal]
|
||||
public delegate void OnCellHoveredEventHandler(Vector2I cell);
|
||||
|
||||
[Signal]
|
||||
public delegate void OnCellSelectedEventHandler(Vector2I cell);
|
||||
|
||||
[Signal]
|
||||
public delegate void OnGridClickedEventHandler(Vector2I cell);
|
||||
|
||||
private const float c_MinZoom = 0.5f;
|
||||
private const float c_MaxZoom = 3.0f;
|
||||
private const int c_TilemapTileSize = 512;
|
||||
private const int c_TopLeftCorner = 1;
|
||||
private const int c_TopRightCorner = 2;
|
||||
private const int c_BottomLeftCorner = 4;
|
||||
private const int c_BottomRightCorner = 8;
|
||||
private const int c_AllCorners = c_TopLeftCorner | c_TopRightCorner | c_BottomLeftCorner | c_BottomRightCorner;
|
||||
private static readonly Vector2I c_InvalidCell = new(-1, -1);
|
||||
private static readonly Color c_BackgroundColor = new(0.06f, 0.07f, 0.08f);
|
||||
private static readonly Color c_WaterColor = new(0.20f, 0.78f, 0.92f);
|
||||
private static readonly Color c_DisabledColor = new(0.32f, 0.34f, 0.35f);
|
||||
private static readonly Color c_ElectricityColor = new(0.96f, 0.78f, 0.20f);
|
||||
private static readonly Color c_FuelColor = new(0.86f, 0.20f, 0.18f);
|
||||
private static readonly Color c_GridLineColor = new(0.36f, 0.41f, 0.45f, 0.35f);
|
||||
private static readonly Color c_HeatColor = new(1.0f, 0.42f, 0.12f);
|
||||
private static readonly Color c_HoverColor = new(0.60f, 0.74f, 0.86f, 0.72f);
|
||||
private static readonly Color c_LeakColor = new(1.0f, 0.27f, 0.16f);
|
||||
private static readonly Color c_ReachableColor = new(0.78f, 0.96f, 0.84f, 0.20f);
|
||||
private static readonly Color c_ReadyColor = new(0.46f, 0.95f, 0.52f);
|
||||
private static readonly Color c_SelectedColor = new(1.0f, 1.0f, 1.0f, 0.95f);
|
||||
private static readonly Color c_UnsafeColor = new(1.0f, 0.9f, 0.25f, 0.9f);
|
||||
|
||||
private bool m_IsPanning;
|
||||
private LevelState? m_LevelState;
|
||||
private Vector2 m_PanOffset;
|
||||
private Texture2D? m_RobotTexture;
|
||||
private Texture2D? m_TerrainTilemap;
|
||||
private int m_TileSize = 48;
|
||||
private float m_Zoom = 1;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://dtsjjdlvxopty
|
||||
49
src/ReactorMaintenance.Godot/Controls/InventoryStrip.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using Godot;
|
||||
|
||||
namespace ReactorMaintenance.Godot.Controls;
|
||||
|
||||
public partial class InventoryStrip : HBoxContainer
|
||||
{
|
||||
public override void _Ready()
|
||||
{
|
||||
AddChild(CreateItem("Fuel Neutralizer", 2, FrontendAssets.FuelIcon, out var fuelLabel));
|
||||
AddChild(CreateItem("Water Neutralizer", 2, FrontendAssets.WaterIcon, out var waterLabel));
|
||||
AddChild(CreateItem("Electric Neutralizer", 1, FrontendAssets.ElectricIcon, out var electricLabel));
|
||||
AddChild(CreateItem("Heat Shield", 1, FrontendAssets.HeatShieldIcon, out var heatLabel));
|
||||
m_FuelLabel = fuelLabel;
|
||||
m_WaterLabel = waterLabel;
|
||||
m_ElectricLabel = electricLabel;
|
||||
m_HeatLabel = heatLabel;
|
||||
}
|
||||
|
||||
public void SetInventory(int fuelNeutralizers, int waterNeutralizers, int electricNeutralizers, int heatShields)
|
||||
{
|
||||
m_FuelLabel.Text = $"{fuelNeutralizers}";
|
||||
m_WaterLabel.Text = $"{waterNeutralizers}";
|
||||
m_ElectricLabel.Text = $"{electricNeutralizers}";
|
||||
m_HeatLabel.Text = $"{heatShields}";
|
||||
}
|
||||
|
||||
private static HBoxContainer CreateItem(string name, int count, string iconPath, out Label countLabel)
|
||||
{
|
||||
var item = new HBoxContainer {
|
||||
SizeFlagsHorizontal = SizeFlags.ExpandFill
|
||||
};
|
||||
item.AddChild(FrontendAssets.CreateIcon(iconPath, new(30, 30)));
|
||||
countLabel = new() {
|
||||
Text = $"{count}",
|
||||
Name = name,
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
SizeFlagsHorizontal = SizeFlags.ExpandFill
|
||||
};
|
||||
item.AddChild(countLabel);
|
||||
return item;
|
||||
}
|
||||
|
||||
private Label m_ElectricLabel = null!;
|
||||
|
||||
private Label m_FuelLabel = null!;
|
||||
private Label m_HeatLabel = null!;
|
||||
|
||||
private Label m_WaterLabel = null!;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://d1pdwv570am3v
|
||||
46
src/ReactorMaintenance.Godot/Controls/LevelHeader.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using Godot;
|
||||
using ReactorMaintenance.Simulation;
|
||||
|
||||
namespace ReactorMaintenance.Godot.Controls;
|
||||
|
||||
public partial class LevelHeader : HBoxContainer
|
||||
{
|
||||
public override void _Ready()
|
||||
{
|
||||
AddChild(m_Title);
|
||||
AddChild(m_Progress);
|
||||
AddChild(m_Badge);
|
||||
AddChild(m_Summary);
|
||||
|
||||
m_Title.SizeFlagsHorizontal = SizeFlags.ExpandFill;
|
||||
m_Title.AddThemeFontSizeOverride("font_size", 24);
|
||||
m_Progress.HorizontalAlignment = HorizontalAlignment.Center;
|
||||
m_Summary.HorizontalAlignment = HorizontalAlignment.Right;
|
||||
}
|
||||
|
||||
public void SetLevel(string title, int levelNumber, int levelCount, ELevelState state)
|
||||
{
|
||||
m_Title.Text = title;
|
||||
m_Progress.Text = $"Level {levelNumber} / {levelCount}";
|
||||
m_Badge.SetState(StateToString(state));
|
||||
m_Summary.Text = "Heat: nominal | Reactor: offline";
|
||||
}
|
||||
|
||||
private static string StateToString(ELevelState state)
|
||||
{
|
||||
return state switch {
|
||||
ELevelState.Stable => "Stable",
|
||||
ELevelState.Caution => "Caution",
|
||||
ELevelState.Critical => "Critical",
|
||||
ELevelState.Ready => "Ready",
|
||||
ELevelState.Lost => "Lost",
|
||||
ELevelState.Won => "Won",
|
||||
_ => "Unknown"
|
||||
};
|
||||
}
|
||||
|
||||
private readonly StateBadge m_Badge = new();
|
||||
private readonly Label m_Progress = new();
|
||||
private readonly Label m_Summary = new();
|
||||
private readonly Label m_Title = new();
|
||||
}
|
||||
1
src/ReactorMaintenance.Godot/Controls/LevelHeader.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cp50vj0rystlk
|
||||
41
src/ReactorMaintenance.Godot/Controls/OutcomeOverlay.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using Godot;
|
||||
|
||||
namespace ReactorMaintenance.Godot.Controls;
|
||||
|
||||
public partial class OutcomeOverlay : PanelContainer
|
||||
{
|
||||
public override void _Ready()
|
||||
{
|
||||
CustomMinimumSize = new(420, 220);
|
||||
AddChild(m_Body);
|
||||
m_Body.AddChild(m_Title);
|
||||
m_Body.AddChild(m_Message);
|
||||
m_Body.AddChild(m_Actions);
|
||||
m_Title.HorizontalAlignment = HorizontalAlignment.Center;
|
||||
m_Title.AddThemeFontSizeOverride("font_size", 26);
|
||||
m_Message.AutowrapMode = TextServer.AutowrapMode.WordSmart;
|
||||
m_Message.HorizontalAlignment = HorizontalAlignment.Center;
|
||||
}
|
||||
|
||||
protected void Configure(string title, string message, IReadOnlyList<(string Text, Action Pressed)> actions)
|
||||
{
|
||||
m_Title.Text = title;
|
||||
m_Message.Text = message;
|
||||
|
||||
foreach (var child in m_Actions.GetChildren())
|
||||
child.QueueFree();
|
||||
|
||||
foreach (var action in actions)
|
||||
{
|
||||
var button = new PrimaryButton();
|
||||
button.Configure(action.Text);
|
||||
button.Pressed += action.Pressed;
|
||||
m_Actions.AddChild(button);
|
||||
}
|
||||
}
|
||||
|
||||
private readonly HBoxContainer m_Actions = new();
|
||||
private readonly VBoxContainer m_Body = new();
|
||||
private readonly Label m_Message = new();
|
||||
private readonly Label m_Title = new();
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://65iow3r0egvo
|
||||
24
src/ReactorMaintenance.Godot/Controls/PrimaryButton.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using Godot;
|
||||
|
||||
namespace ReactorMaintenance.Godot.Controls;
|
||||
|
||||
public partial class PrimaryButton : Button
|
||||
{
|
||||
public override void _Ready()
|
||||
{
|
||||
FocusMode = FocusModeEnum.All;
|
||||
CustomMinimumSize = new(220, 44);
|
||||
SizeFlagsHorizontal = SizeFlags.ExpandFill;
|
||||
|
||||
Icon = FrontendAssets.LoadTexture(FrontendAssets.PrimaryButtonAccent);
|
||||
ExpandIcon = true;
|
||||
IconAlignment = HorizontalAlignment.Left;
|
||||
}
|
||||
|
||||
public void Configure(string text, string tooltip = "", bool disabled = false)
|
||||
{
|
||||
Text = text;
|
||||
TooltipText = tooltip;
|
||||
Disabled = disabled;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://bxk3mw5bybqxr
|
||||
51
src/ReactorMaintenance.Godot/Controls/StateBadge.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using Godot;
|
||||
|
||||
namespace ReactorMaintenance.Godot.Controls;
|
||||
|
||||
public partial class StateBadge : PanelContainer
|
||||
{
|
||||
public override void _Ready()
|
||||
{
|
||||
CustomMinimumSize = new(96, 28);
|
||||
|
||||
var stack = new Control {
|
||||
CustomMinimumSize = CustomMinimumSize,
|
||||
SizeFlagsHorizontal = SizeFlags.ExpandFill
|
||||
};
|
||||
AddChild(stack);
|
||||
|
||||
var frame = new TextureRect {
|
||||
Texture = FrontendAssets.LoadTexture(FrontendAssets.StateBadgeFrame),
|
||||
AnchorRight = 1,
|
||||
AnchorBottom = 1,
|
||||
ExpandMode = TextureRect.ExpandModeEnum.FitWidthProportional,
|
||||
StretchMode = TextureRect.StretchModeEnum.Scale,
|
||||
MouseFilter = MouseFilterEnum.Ignore
|
||||
};
|
||||
stack.AddChild(frame);
|
||||
|
||||
m_Text.SetAnchorsPreset(LayoutPreset.FullRect);
|
||||
m_Text.HorizontalAlignment = HorizontalAlignment.Center;
|
||||
m_Text.VerticalAlignment = VerticalAlignment.Center;
|
||||
stack.AddChild(m_Text);
|
||||
}
|
||||
|
||||
public void SetState(string state)
|
||||
{
|
||||
m_Text.Text = state;
|
||||
m_Text.AddThemeColorOverride("font_color", GetColor(state));
|
||||
}
|
||||
|
||||
private static Color GetColor(string state)
|
||||
{
|
||||
return state.ToLowerInvariant() switch {
|
||||
"stable" => new(0.72f, 0.86f, 0.76f),
|
||||
"caution" => new(1.0f, 0.76f, 0.24f),
|
||||
"critical" or "lost" => new(1.0f, 0.36f, 0.32f),
|
||||
"ready" or "won" => new(0.45f, 1.0f, 0.58f),
|
||||
_ => Colors.White
|
||||
};
|
||||
}
|
||||
|
||||
private readonly Label m_Text = new();
|
||||
}
|
||||
1
src/ReactorMaintenance.Godot/Controls/StateBadge.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://y7qn5eixkr7j
|
||||
9
src/ReactorMaintenance.Godot/Data/CampaignLevel.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace ReactorMaintenance.Godot.Data;
|
||||
|
||||
public sealed record CampaignLevel
|
||||
{
|
||||
public string Id { get; init; } = string.Empty;
|
||||
public string Name { get; init; } = string.Empty;
|
||||
public string FlavorText { get; init; } = string.Empty;
|
||||
public string LevelPath { get; init; } = string.Empty;
|
||||
}
|
||||
1
src/ReactorMaintenance.Godot/Data/CampaignLevel.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://uis6i7yfkrty
|
||||
6
src/ReactorMaintenance.Godot/Data/CampaignManifest.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace ReactorMaintenance.Godot.Data;
|
||||
|
||||
public sealed record CampaignManifest
|
||||
{
|
||||
public IReadOnlyList<CampaignLevel> Levels { get; init; } = Array.Empty<CampaignLevel>();
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://bnyc3qlyksmua
|
||||
39
src/ReactorMaintenance.Godot/Data/CampaignRepository.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using FileAccess = Godot.FileAccess;
|
||||
|
||||
namespace ReactorMaintenance.Godot.Data;
|
||||
|
||||
public static class CampaignRepository
|
||||
{
|
||||
public static CampaignManifest LoadDefault()
|
||||
{
|
||||
var json = FileAccess.GetFileAsString(c_DefaultManifestPath);
|
||||
if (string.IsNullOrWhiteSpace(json))
|
||||
return CreateFallback();
|
||||
|
||||
var manifest = JsonSerializer.Deserialize<CampaignManifest>(json, s_Options);
|
||||
return manifest is { Levels.Count: > 0 } ? manifest : CreateFallback();
|
||||
}
|
||||
|
||||
private static CampaignManifest CreateFallback()
|
||||
{
|
||||
return new() {
|
||||
Levels = [
|
||||
new() {
|
||||
Id = "fallback",
|
||||
Name = "Fallback Reactor",
|
||||
FlavorText = "A placeholder level loaded because the campaign manifest was unavailable.",
|
||||
LevelPath = "res://Data/Levels/fallback.json"
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
private const string c_DefaultManifestPath = "res://Data/default_campaign_manifest.json";
|
||||
|
||||
private static readonly JsonSerializerOptions s_Options = new() {
|
||||
PropertyNameCaseInsensitive = true,
|
||||
Converters = { new JsonStringEnumConverter() }
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://bufo8npdbreb1
|
||||
62
src/ReactorMaintenance.Godot/Data/FrontendSession.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
namespace ReactorMaintenance.Godot.Data;
|
||||
|
||||
public sealed class FrontendSession
|
||||
{
|
||||
public FrontendSession(CampaignManifest campaign)
|
||||
{
|
||||
m_Campaign = campaign;
|
||||
}
|
||||
|
||||
public void StartNewCampaign()
|
||||
{
|
||||
IsRandomLevel = false;
|
||||
CampaignIndex = 0;
|
||||
HasContinue = true;
|
||||
}
|
||||
|
||||
public void ContinueCampaign()
|
||||
{
|
||||
IsRandomLevel = false;
|
||||
HasContinue = true;
|
||||
}
|
||||
|
||||
public void StartRandomLevel()
|
||||
{
|
||||
IsRandomLevel = true;
|
||||
}
|
||||
|
||||
public void MarkCurrentLevelLoaded()
|
||||
{
|
||||
if (!IsRandomLevel)
|
||||
HasContinue = true;
|
||||
}
|
||||
|
||||
public void AdvanceToNextLevel()
|
||||
{
|
||||
if (HasNextLevel)
|
||||
CampaignIndex++;
|
||||
}
|
||||
|
||||
public void CompleteCampaign()
|
||||
{
|
||||
IsRandomLevel = false;
|
||||
CampaignIndex = 0;
|
||||
HasContinue = false;
|
||||
}
|
||||
|
||||
public bool HasContinue { get; private set; }
|
||||
public bool IsRandomLevel { get; private set; }
|
||||
public int CampaignIndex { get; private set; }
|
||||
public CampaignLevel CurrentLevel => IsRandomLevel ? m_RandomLevel : m_Campaign.Levels[CampaignIndex];
|
||||
public int LevelNumber => IsRandomLevel ? 1 : CampaignIndex + 1;
|
||||
public int LevelCount => IsRandomLevel ? 1 : m_Campaign.Levels.Count;
|
||||
public bool HasNextLevel => !IsRandomLevel && CampaignIndex + 1 < m_Campaign.Levels.Count;
|
||||
private readonly CampaignManifest m_Campaign;
|
||||
|
||||
private readonly CampaignLevel m_RandomLevel = new() {
|
||||
Id = "random-maintenance",
|
||||
Name = "Random Maintenance Shift",
|
||||
FlavorText = "A generated shift will use authored JSON level data until generation is implemented.",
|
||||
LevelPath = "res://Data/Levels/random_placeholder.json"
|
||||
};
|
||||
}
|
||||
1
src/ReactorMaintenance.Godot/Data/FrontendSession.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bsbukw3qpo0by
|
||||
197
src/ReactorMaintenance.Godot/Data/GameSession.cs
Normal file
@@ -0,0 +1,197 @@
|
||||
using ReactorMaintenance.Simulation;
|
||||
|
||||
namespace ReactorMaintenance.Godot.Data;
|
||||
|
||||
public sealed class GameSession
|
||||
{
|
||||
public event StateChangedHandler? LevelStateChanged;
|
||||
public event StateChangedHandler? RobotMoved;
|
||||
public event StateChangedHandler? PulseAdvanced;
|
||||
public event StateChangedHandler? LevelWon;
|
||||
public event StateChangedHandler? LevelLost;
|
||||
|
||||
public void Initialize(LevelState levelState, string levelPath)
|
||||
{
|
||||
LevelState = levelState;
|
||||
LevelPath = levelPath;
|
||||
m_StartSnapshot = LevelSerializer.Deserialize(LevelSerializer.Serialize(levelState));
|
||||
}
|
||||
|
||||
public bool MoveRobot(GridPosition destination)
|
||||
{
|
||||
if (!LevelState.InBounds(destination) || !LevelState.IsFloor(destination) || LevelState.Robot.Position.ManhattanDistance(destination) != 1)
|
||||
return false;
|
||||
|
||||
var previousPosition = LevelState.Robot.Position;
|
||||
LevelState = m_Engine.MoveRobot(LevelState, destination);
|
||||
if (LevelState.Robot.Position == previousPosition)
|
||||
return false;
|
||||
|
||||
RobotMoved?.Invoke(this);
|
||||
LevelStateChanged?.Invoke(this);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool InteractProp()
|
||||
{
|
||||
if (LevelState.Global.LevelState is ELevelState.Lost or ELevelState.Won)
|
||||
return false;
|
||||
|
||||
var pulse = LevelState.Global.Pulse;
|
||||
var trace = m_Engine.InteractPropWithPulseTrace(LevelState);
|
||||
LevelState = trace.FinalState;
|
||||
LastPulseSteps = trace.Steps;
|
||||
LastPulseFeedback = CreatePulseFeedback(LevelState);
|
||||
if (LevelState.Global.Pulse == pulse)
|
||||
{
|
||||
LevelStateChanged?.Invoke(this);
|
||||
return false;
|
||||
}
|
||||
|
||||
OnPulseAdvanced();
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool InteractLeak(ECarrierType carrier, bool useRemedy)
|
||||
{
|
||||
if (LevelState.Global.LevelState is ELevelState.Lost or ELevelState.Won)
|
||||
return false;
|
||||
|
||||
var pulse = LevelState.Global.Pulse;
|
||||
var trace = m_Engine.InteractLeakWithPulseTrace(LevelState, carrier, useRemedy);
|
||||
LevelState = trace.FinalState;
|
||||
LastPulseSteps = trace.Steps;
|
||||
LastPulseFeedback = CreatePulseFeedback(LevelState);
|
||||
if (LevelState.Global.Pulse == pulse)
|
||||
{
|
||||
LevelStateChanged?.Invoke(this);
|
||||
return false;
|
||||
}
|
||||
|
||||
OnPulseAdvanced();
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool ApplyHeatShield()
|
||||
{
|
||||
if (LevelState.Global.LevelState is ELevelState.Lost or ELevelState.Won)
|
||||
return false;
|
||||
|
||||
var pulse = LevelState.Global.Pulse;
|
||||
var trace = m_Engine.ApplyHeatShieldWithPulseTrace(LevelState);
|
||||
LevelState = trace.FinalState;
|
||||
LastPulseSteps = trace.Steps;
|
||||
LastPulseFeedback = CreatePulseFeedback(LevelState);
|
||||
if (LevelState.Global.Pulse == pulse)
|
||||
{
|
||||
LevelStateChanged?.Invoke(this);
|
||||
return false;
|
||||
}
|
||||
|
||||
OnPulseAdvanced();
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool ActivateReactor()
|
||||
{
|
||||
if (LevelState.Global.LevelState is ELevelState.Lost or ELevelState.Won)
|
||||
return false;
|
||||
|
||||
LevelState = m_Engine.ActivateReactor(LevelState);
|
||||
CheckOutcome();
|
||||
return true;
|
||||
}
|
||||
|
||||
public IReadOnlyList<Forecast> GetForecasts()
|
||||
{
|
||||
return m_Engine.Forecast(LevelState);
|
||||
}
|
||||
|
||||
public void Retry()
|
||||
{
|
||||
LevelState = m_StartSnapshot;
|
||||
LastPulseSteps = Array.Empty<LevelState>();
|
||||
LastPulseFeedback = Array.Empty<string>();
|
||||
LevelStateChanged?.Invoke(this);
|
||||
}
|
||||
|
||||
public void ApplyEditorTool(GridPosition position, EditorToolCommand command)
|
||||
{
|
||||
LevelState = LevelEditor.Apply(LevelState, position, command);
|
||||
LevelStateChanged?.Invoke(this);
|
||||
}
|
||||
|
||||
public string ValidateForSave()
|
||||
{
|
||||
var report = m_Validator.Validate(LevelState);
|
||||
if (!report.IsValid)
|
||||
return report.Errors[0].Message;
|
||||
|
||||
return report.Warnings.Count > 0 ? report.Warnings[0].Message : "Level is valid.";
|
||||
}
|
||||
|
||||
public void SaveLevel()
|
||||
{
|
||||
LevelStateLoader.Save(LevelPath, LevelState);
|
||||
m_StartSnapshot = LevelSerializer.Deserialize(LevelSerializer.Serialize(LevelState));
|
||||
}
|
||||
|
||||
private void OnPulseAdvanced()
|
||||
{
|
||||
CheckOutcome();
|
||||
PulseAdvanced?.Invoke(this);
|
||||
}
|
||||
|
||||
private static IReadOnlyList<string> CreatePulseFeedback(LevelState level)
|
||||
{
|
||||
var feedback = new List<string>();
|
||||
if (level.Global.LevelState == ELevelState.Ready)
|
||||
feedback.Add("Reactor ready");
|
||||
|
||||
if (level.Props.Any(prop => prop.Type == EPropType.Consumer && prop.ServiceStateFor(ECarrierType.Fuel) == EConsumerServiceState.Starved))
|
||||
feedback.Add("Fuel consumer starved");
|
||||
|
||||
if (level.Props.Any(prop => prop.Type == EPropType.Consumer && prop.ServiceStateFor(ECarrierType.Water) == EConsumerServiceState.Starved))
|
||||
feedback.Add("Water consumer starved");
|
||||
|
||||
if (level.Props.Any(prop => prop.Type == EPropType.Consumer && prop.ServiceStateFor(ECarrierType.Electricity) == EConsumerServiceState.Starved))
|
||||
feedback.Add("Electricity consumer starved");
|
||||
|
||||
if (level.Surface.Any(surface => surface.IsWetElectricUnsafe()))
|
||||
feedback.Add("Wet-electric risk");
|
||||
|
||||
if (level.Surface.Any(surface => surface.Heat >= Balancing.Current.RobotHeatSafetyThreshold))
|
||||
feedback.Add("Heat danger");
|
||||
|
||||
return feedback.Count > 0 ? feedback : [level.Global.Status];
|
||||
}
|
||||
|
||||
private void CheckOutcome()
|
||||
{
|
||||
if (LevelState.Global.LevelState == ELevelState.Won)
|
||||
LevelWon?.Invoke(this);
|
||||
else if (LevelState.Global.LevelState == ELevelState.Lost)
|
||||
LevelLost?.Invoke(this);
|
||||
}
|
||||
|
||||
public LevelState LevelState { get; private set; } = null!;
|
||||
public GridPosition RobotPosition => LevelState.Robot.Position;
|
||||
public GlobalState GlobalState => LevelState.Global;
|
||||
public IReadOnlyList<Forecast> Forecasts => LevelState.Forecasts;
|
||||
public IReadOnlyList<string> LastPulseFeedback { get; private set; } = Array.Empty<string>();
|
||||
public IReadOnlyList<LevelState> LastPulseSteps { get; private set; } = Array.Empty<LevelState>();
|
||||
public IReadOnlyList<LeakState> Leaks => LevelState.Leaks;
|
||||
public string LevelPath { get; private set; } = string.Empty;
|
||||
public IReadOnlyList<ReactorState> Reactors => LevelState.Reactors;
|
||||
public IReadOnlyList<PropState> Props => LevelState.Props;
|
||||
public ECellTerrain[] Terrain => LevelState.Terrain;
|
||||
public SurfaceState[] Surface => LevelState.Surface;
|
||||
public UndergroundCell[] UndergroundFuel => LevelState.Fuel;
|
||||
public UndergroundCell[] UndergroundWater => LevelState.Water;
|
||||
public UndergroundCell[] UndergroundElectricity => LevelState.Electricity;
|
||||
public delegate void StateChangedHandler(GameSession sender);
|
||||
|
||||
private readonly SimulationEngine m_Engine = new();
|
||||
private readonly LevelValidator m_Validator = new();
|
||||
private LevelState m_StartSnapshot = null!;
|
||||
}
|
||||
1
src/ReactorMaintenance.Godot/Data/GameSession.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://c32a61f2pslf8
|
||||
50
src/ReactorMaintenance.Godot/Data/LevelStateLoader.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using ReactorMaintenance.Simulation;
|
||||
using FileAccess = Godot.FileAccess;
|
||||
|
||||
namespace ReactorMaintenance.Godot.Data;
|
||||
|
||||
public static class LevelStateLoader
|
||||
{
|
||||
public static LevelState Load(string path)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
throw new ArgumentException("Level path must not be null or empty.", nameof(path));
|
||||
|
||||
if (!FileAccess.FileExists(path))
|
||||
throw new FileNotFoundException($"Level file not found: {path}");
|
||||
|
||||
var json = FileAccess.GetFileAsString(path);
|
||||
if (string.IsNullOrWhiteSpace(json))
|
||||
throw new InvalidOperationException($"Level file is empty: {path}");
|
||||
|
||||
try
|
||||
{
|
||||
return LevelSerializer.Deserialize(json);
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Failed to deserialize level from {path}: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Save(string path, LevelState level)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
throw new ArgumentException("Level path must not be null or empty.", nameof(path));
|
||||
|
||||
var report = new LevelValidator().Validate(level);
|
||||
if (!report.IsValid)
|
||||
throw new InvalidOperationException(report.Errors[0].Message);
|
||||
|
||||
using var file = FileAccess.Open(path, FileAccess.ModeFlags.Write);
|
||||
if (file is null)
|
||||
throw new IOException($"Could not open level file for writing: {path}");
|
||||
|
||||
file.StoreString(LevelSerializer.Serialize(level));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://dded4q1wrmfdr
|
||||