# Trail into Darkness (Nox)

A party-based exploration RPG demo built in **Unity 6 (6000.3.7f1)** with URP. The player travels a world map, manages supplies and time, and resolves randomized encounters. The codebase is the focus here — the gameplay is a vertical slice meant to exercise the architecture.

> This is a portfolio demo. It showcases a hand-rolled application framework, clean separation of concerns, and a set of reusable in-house packages — not a finished game.

---

## At a Glance

- **Engine:** Unity 6, Universal Render Pipeline
- **Input:** new Input System
- **Asset loading:** Addressables (everything content-facing is loaded by address/reference)
- **Config:** data-driven via `ScriptableObject` settings assets
- **Language:** C# 9+ (nullable enabled in core gameplay)
- **Localization, Test Framework, Memory Profiler** wired in

---

## Goals

The architecture is built around a few deliberate goals, aimed at production workflows for small-to-medium teams:

- **Scene-independent.** Logic does not depend on a particular scene being set up by hand. Scenes are data; the framework boots and wires itself regardless of where you press Play.
- **Core abstracted from gameplay.** Gameplay coders work inside relatively isolated, modular "feature cocoons" against clean interfaces, without needing to understand boot, platform, or state-management internals.
- **Built to extend.** Adding or modifying features — new play modes, systems, or DLC-style content — should be additive and low-risk, not a rewrite.
- **Testable by design.** POCO (Plain Old CLR Object — a plain C# class with no `MonoBehaviour`/engine dependency) logic and interface boundaries keep systems coverable by both editor tests and play-mode tests, decoupled from the engine.
- **Team-friendly.** Code lives in C# and small assets rather than large monolithic scenes/prefabs, reducing the asset-locking and merge conflicts that stall multi-person teams.
- **Swappable systems.** Concrete implementations sit behind interfaces (save, platform, input, camera, transitions, …) so any one system can be replaced without touching its callers.

---

## Architecture

The project runs on a custom state machine with a **single Unity update driver**. Almost all gameplay logic lives in plain C# classes (POCOs) that are ticked manually — `MonoBehaviour` is used only where Unity forces it.

### Boot flow

```
Boot (RuntimeInitializeOnLoadMethod)
  ├─ platform initialization (early, pre-Initializer)
  └─ instantiates "Initializer" Addressable
       └─ EntryPoint (MonoBehaviour)
            ├─ loads InitializerSettingsFile + BootstrapReferences
            ├─ selects + initializes IPlatform (Desktop / UnityEditor)
            ├─ builds GameDataState + the IGameState lookup
            └─ spawns GameStateRunner ── drives everything
```

> **Platform initialization runs in both `Boot` and `EntryPoint`.** `Boot` establishes the platform context early so that platform-specific data is available before the Initializer loads, and `EntryPoint` then selects and fully initializes the concrete `IPlatform` for the rest of the runtime. This two-step setup guarantees the correct platform-specific data is provided at every stage of boot.

### Layers

- **`Boot` / `EntryPoint`** — entry point and dependency wiring. Loads settings, picks the platform, constructs the game states, hands off to the runner.
- **`GameStateRunner`** — the *only* class that uses Unity's `Update`/`FixedUpdate`/`LateUpdate`. Ticks the active state and owns screen-fade transitions between states.
- **Game States (`IGameState`)** — top-level application modes: `BootState` (splash), `MainMenu`, `GameMode`. Each implements `EnterGameState / Tick / LateTick / ExitGameState / Dispose`.
- **Play Modes (`IPlayMode`)** — gameplay sub-modes living inside `GameMode`: `Adventure`, `Town`, `Rest`, `Combat`, `PauseMenu`. `GameModeGameState` handles switching, caching, and pause suspend/resume.

### How the state machines run

There are two nested state machines, and at any moment **exactly one branch of each is live**:

- `GameStateRunner` keeps a single *active* `IGameState` and ticks only that one. The others simply exist in a lookup and consume nothing.
- Inside `GameMode`, `GameModeGameState` keeps a single *active* `IPlayMode` and ticks only that one. Inactive modes are kept in a cache (or suspended), dormant, until switched back in.

This keeps each part **isolated and self-contained** — a state or play mode owns its own setup, update, and teardown, and never reaches into another's lifecycle. It also means **no resources are spent on ticks that don't matter**:

- Nothing ticks until it reports ready (`IsGameStateInitialized` / `IsGameModeInitialized`); half-loaded systems stay idle instead of running against missing data.
- Ticks are frozen entirely during fade transitions, so nothing runs mid-swap.
- Pausing **suspends** the underlying play mode rather than tearing it down — it stops ticking but keeps its state, so resume is instant and allocation-free.
- Switching play modes reuses the cached instance when possible, avoiding reload/re-alloc churn.

The result is one predictable update per frame: one state, one play mode, with everything else parked.

### Design principles

- **One update loop.** All logic is driven from `GameStateRunner` via manual ticks — predictable order, no scattered `Update()` methods.
- **Constructor injection.** States and play modes receive their dependencies explicitly; no service locators or singletons in gameplay.
- **Data-driven.** Behavior is configured through `ScriptableObject` settings (`*Settings`, `*References`) loaded as Addressables, not hardcoded.
- **Interface-first.** `IGameState`, `IPlayMode`, `IPlatform`, `ICameraController`, `ISceneTransition`, save-system interfaces — concrete types are swappable.
- **Platform abstraction.** `IPlatform` + `PlatformSelector` isolate input/platform differences (Desktop vs. Editor).
- **Allocation-aware.** Uses `ZLinq` for allocation-free LINQ on hot paths.

---

## UI architecture

The UI follows the same philosophy as the rest of the codebase: **logic lives in plain C#, `MonoBehaviour` is only a thin bridge to the scene.** Each screen is split into two pieces:

- **`*References` (MonoBehaviour)** — a dumb scene-authoring component that just holds serialized links to the actual widgets (buttons, images, TMP texts, containers). No logic. Examples: `GuiReferences`, `PauseMenuReferences`, `MainMenuReference`.
- **`*View` (POCO, implements `IMenuView`)** — the view logic. It receives its references and the data it renders via constructor injection, and exposes `Initialize / Show / Hide / Tick`. Examples: `AdventureView`, `PauseMenuView`, `PartyGuiView`.

Key points:

- **Manually ticked.** Views are driven by their owning play mode (e.g. `AdventurePlayMode.Tick()` calls `adventureView.Tick()`) — consistent with the single-update-loop model, no hidden `Update()` methods.
- **One-way coupling.** UI never reaches into game logic directly; it requests changes through `GameDataState` (e.g. a button calls `ChangePlayMode(PlayMode.PauseMenu)`). State drives UI, UI signals intent.
- **Lazy, Addressable-loaded.** UI prefabs are loaded by address and instantiated on demand (`PauseMenuView` pulls `PauseMenuPrefabs`); references already present in the scene are reused via `FindFirstObjectByType`.
- **Allocation-aware updates.** Views update only what changed — `AdventureView` rewrites text only when day/time changes; `PartyGuiView` uses a fixed-size **slot pool** (activate/deactivate, never instantiate per frame) and rebuilds only when party composition changes.
- **Data-driven popups.** Rich tooltips/popups are composed through a builder (`PopupContentBuilder`) backed by the `Jovian.PopupSystem` package, so content is declared, not hand-wired per widget.
- Built on **uGUI + TextMeshPro**.

---

## In-house packages (`Packages/com.jovian.*`)

Reusable systems are split into local packages, each with Runtime/Editor (and some Tests) assembly definitions:

| Package | Purpose |
|---|---|
| `save-system` | Save/load with JSON or obfuscated binary, slots, file-system storage |
| `encounter-system` | Encounter tables, registries, quest progress/log |
| `zone-system` | Spatial zones used to trigger encounters during travel |
| `calendar` | World clock, day phases, in-game time |
| `popup-system` | Categorized UI popups |
| `ingame-logging` / `logger` | In-game event log + general logging |
| `tag-system` | Lightweight tagging |
| `inspector-tools` / `assets-history` / `unitypackagesync` | Editor tooling |
| `utilities` | Shared helpers |

Third-party: `ZLinq` (zero-alloc LINQ), `ayellowpaper.serialized-dictionary`.

---

## Project layout

```
Assets/
  Code/
    Core/         Boot, EntryPoint, GameStateRunner, IGameState, settings
    GameState/
      Camera/     camera controller + settings
      Entities/   characters, parties, stats, perks, modifiers (factories + registries)
      PlayModes/  Adventure, Town, Rest, Combat, Pause + encounters, map, time
      UI/         views and GUI references
    Input/        IInput, desktop/editor input + settings
    Platform/     IPlatform, desktop/editor platforms + settings
    SplashMainMenuUI/  splash + main menu views
    Util/         BootMode (editor), SceneReference editor
  Scenes/         Startup, MainMenu, Adventure, Combat
  Art/            map, UI, animations
Packages/         in-house com.jovian.* packages + third-party
```

---

## Running the project

1. Open with **Unity 6000.3.7f1**.
2. Press Play — boot behavior depends on the boot mode (below). Default, play from any scene.

### Boot modes — start from any scene

The project can launch from any scene. This is controlled by the **`Nox/Boot`** editor menu, which sets a `BootType`:

- **Full Boot** — loads the `Startup` scene first, then the normal boot chain. Closest to a real build.
- **Scene Boot** — instantiates the `Initializer` directly into the *current* scene, so you can press Play on `Adventure`, `MainMenu`, etc. and boot in place.
- **Unity Default** — no boot injection; plays the scene as-is (raw Unity behavior).

> In a player build, the full boot chain always runs.

### Scene authoring (`SceneReference`)

Each standalone-playable scene carries a `SceneReference` component declaring its entry point:

- `gameState` — which `IGameState` the scene belongs to (e.g. `GameMode`)
- `playMode` — which `IPlayMode` to start in (e.g. `Adventure`)

`EntryPoint` reads this on boot to start in the right state. If a scene has no `SceneReference`, it defaults to `BootState`.

---

## Notes for reviewers

- Start with `EntryPoint.cs` → `GameStateRunner.cs` → `GameModeGameState.cs` to follow the control flow top-down.
- `AdventurePlayMode.cs` is the most representative gameplay file — it shows how a play mode composes handlers (movement, time, encounters, inventory, UI) and integrates the in-house packages.
- Save/load round-trips through `NoxSaveData` + `Jovian.SaveSystem`.

---

## Status

This is a **vertical slice**. The application framework, boot/state flow, save system, and adventure loop are in place, but:

- The **combat system is not implemented** — `Combat` exists as a play mode hook only.
- **Design data is still minimal** — zones, encounters, encounter tables, and related content are placeholders rather than authored, balanced data.

The intent is to demonstrate the architecture and systems, with content authoring and combat as the next steps.

---

## License

**Copyright © 2026 Sebastian Bularca. All Rights Reserved.**

This repository is proprietary. It is shared for **evaluation purposes only** (e.g. portfolio or hiring review): you may view and clone it, but you may **not** reproduce, modify, distribute, or reuse any part of it. See [LICENSE](LICENSE) for full terms.
