From 772b555578ec4f89fdecbf7ba1f6539f02e861f6 Mon Sep 17 00:00:00 2001 From: Samuel Lison Date: Fri, 19 Jun 2026 13:30:12 +1000 Subject: [PATCH] Document survival stats; lint the HUD loader in CI Add docs/survival-stats.md (the stat model, no-code SurvivalStatsConfig tuning, the SurvivorStatBar/Stat contract, the Rojo one-way-sync caveat) and cross-link it. CHANGELOG Unreleased entry. CI + CONTRIBUTING now run stylua/luau-lsp over assets/ so the HUD loader is checked. Co-Authored-By: Claude Opus 4.8 --- .github/workflows/ci.yml | 4 +- CHANGELOG.md | 10 ++++ CONTRIBUTING.md | 4 +- docs/architecture.md | 2 + docs/extending.md | 3 +- docs/survival-stats.md | 115 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 133 insertions(+), 5 deletions(-) create mode 100644 docs/survival-stats.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 17cff8b..e215b6b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,7 +26,7 @@ jobs: uses: CompeyDev/setup-rokit@v0.1.2 - name: StyLua (format check) - run: stylua --check src demo + run: stylua --check src demo assets - name: Selene (lint) run: selene . @@ -49,7 +49,7 @@ jobs: --ignore "Packages/**" \ --ignore "DevPackages/**" \ --ignore "ServerPackages/**" \ - src demo + src demo assets/client # Prove both Rojo targets build: the drop-in engine model and the runnable demo place. - name: Build engine model (default.project.json) diff --git a/CHANGELOG.md b/CHANGELOG.md index 79925bb..eee67e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,16 @@ is promoted to the new version and `main` is tagged `vX.Y.Z`. ## Unreleased ### Added +- **Survival-stats engine + built-in HUD** (#8, #2) — a server tick simulates per-stat + drain/regen, stored as auto-replicating Player Attributes, and a reactive **top-left HUD** + renders them (no RemoteEvents). The HUD is a real, designer-editable `SurvivalHud` ScreenGui + in StarterGui — restyle it in Studio with zero code; bars bind by a `Stat` attribute and a + `Fill` child. Ships with seven default stats (health/energy/hunger/thirst/fatigue/blood/ + poison), tunable **without code** via a `SurvivalStatsConfig` Configuration instance (or, for + developers, `Config.override("SurvivalStats", …)` / `Stats.defineStat`). Works out of the box + for every distribution — the demo/Rojo source mount it, the drop-in `.rbxm` auto-installs it + on `start()`, and a runtime fallback guarantees a HUD always appears. Adds the engine's first + client layer (`SurvivorCore.startClient()`). See [docs/survival-stats.md](docs/survival-stats.md). - **Continuous integration** (`.github/workflows/ci.yml`) — every push to `main`/`dev` and every PR runs `stylua --check`, `selene`, `luau-lsp analyze` (against a Rojo sourcemap + Roblox type defs), then builds **both** `default.project.json` (the drop-in engine model) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9e93179..a89386e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -113,12 +113,12 @@ published (which auto-posts to Discussions → Announcements). obvious. Keep the engine content-free. 4. **Test locally.** Play-test in Studio, then run the same checks CI does: ```bash - stylua --check src demo + stylua --check src demo assets selene . rojo sourcemap demo.project.json --output sourcemap.json curl -fsSL -o globalTypes.d.luau https://raw.githubusercontent.com/JohnnyMorganz/luau-lsp/main/scripts/globalTypes.d.luau luau-lsp analyze --sourcemap sourcemap.json --defs globalTypes.d.luau --no-strict-dm-types \ - --ignore "Packages/**" --ignore "DevPackages/**" --ignore "ServerPackages/**" src demo + --ignore "Packages/**" --ignore "DevPackages/**" --ignore "ServerPackages/**" src demo assets/client rojo build default.project.json --output SurvivorCore.rbxm rojo build demo.project.json --output demo.rbxl ``` diff --git a/docs/architecture.md b/docs/architecture.md index 8d1c01a..60c1c09 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -56,4 +56,6 @@ layer → harvesting/wildlife sub-engine. - [Getting Started](getting-started.md) — install via Rojo + Wally or the drop-in `.rbxm`. - [Extending SurvivorCore](extending.md) — the `register()` API, the component/attribute model, and Hooks. +- [Survival Stats + HUD](survival-stats.md) — the stat simulation, no-code tuning, and the + designer-editable HUD. - [CONTRIBUTING](../CONTRIBUTING.md) — local dev setup, code style, and the branching model. diff --git a/docs/extending.md b/docs/extending.md index 480f35a..59d2d88 100644 --- a/docs/extending.md +++ b/docs/extending.md @@ -219,5 +219,6 @@ end) SurvivorCore.start() ``` -See [Getting Started](getting-started.md) to wire the engine into your place, and +See [Getting Started](getting-started.md) to wire the engine into your place, +[Survival Stats + HUD](survival-stats.md) for the built-in stats and HUD, and [Architecture](architecture.md) for how the layers fit together. diff --git a/docs/survival-stats.md b/docs/survival-stats.md new file mode 100644 index 0000000..3eab10a --- /dev/null +++ b/docs/survival-stats.md @@ -0,0 +1,115 @@ +# Survival Stats + HUD + +SurvivorCore ships a survival-stat simulation and a reactive **top-left HUD**. It's batteries- +included — register nothing and you already get seven stats and a working HUD — but every rate +is tunable, and the HUD is a real ScreenGui you restyle in Studio with **zero code**. + +## How it works + +- Each stat value lives as a **Roblox Player Attribute** on the player (e.g. `Hunger`). Roblox + replicates attributes to that player's client automatically, so the HUD reads them directly — + **no RemoteEvents**. +- A server tick (10 Hz) moves each stat by its signed **rate per second** toward `0` or its + `Max`, clamped. +- The HUD binds each bar to its stat's attribute and re-renders on change. + +`SurvivorCore.start()` (server) boots the simulation. The client HUD is booted by +`SurvivorCore.startClient()`, which the built-in HUD's loader does for you. + +## The default stats + +| Stat | Start | Rises to bad? | Behaviour in this version | +|---|---|---|---| +| Health | 100 | no (0 = bad) | display-only | +| Energy | 100 | no (0 = bad) | display-only | +| Hunger | 0 | **yes** (100 = starving) | rises over ~30 min | +| Thirst | 0 | **yes** (100 = dehydrated) | rises over ~20 min | +| Fatigue | 0 | **yes** (100 = exhausted) | rises over ~60 min | +| Poison | 0 | **yes** (100 = bad) | inert until a poison source | +| Blood | 100 | no (0 = death) | inert until a bleed source | + +> Consequences (hunger/thirst draining energy, energy draining health, blood = 0 → death, …) +> and the energy/movement coupling arrive in later versions. This version is the stat +> simulation + HUD + tuning. + +`Invert` (per stat) controls only the HUD: an inverted bar shows the *healthy* amount, so Hunger +(0 = fed) renders as a bar that **depletes** as you get hungry. + +## Tuning — no code (recommended for creators) + +The engine ships a **`SurvivalStatsConfig`** `Configuration` instance (in `ReplicatedStorage`). +It has one child `Configuration` per stat; edit their **Attributes** in Studio — no Lua: + +| Attribute | Meaning | +|---|---| +| `RatePerSecond` | signed change per second (`+` rises toward `Max`, `−` falls toward 0) | +| `Max` / `Start` | range and spawn value | +| `WarnAt` | warn when the displayed bar drops below this percent | +| `Invert` | bar full = healthy | +| `Display` | show this stat in the HUD | + +Edits take effect **live** — drag `Thirst.RatePerSecond` up and thirst drains faster +immediately. + +## Tuning — code (for developers) + +Both work, and both lose to the Studio `SurvivalStatsConfig` instance if it's present: + +```lua +-- tweak shipped rates +SurvivorCore.Config.override("SurvivalStats", { + Thirst = { ratePerSecond = 0.10 }, + Hunger = { ratePerSecond = 0 }, +}) + +-- or add your own stat (it gets a HUD bar if a bar binds it) +SurvivorCore.Stats.defineStat({ + name = "Sanity", start = 100, max = 100, ratePerSecond = -0.05, invert = false, warnAt = 25, display = true, +}) +``` + +**Precedence (last wins):** engine defaults → `Config.override` → the Studio `SurvivalStatsConfig` +instance. + +## The HUD — restyle it freely + +The HUD is the `SurvivalHud` ScreenGui in **StarterGui**. Restyle anything — colors, gradients, +textures, position, fonts, add or remove bars — in Studio. The engine only ever drives each +bar's fill, so your styling is untouched. + +**A stat bar** is any `GuiObject` that: +- carries a **`Stat`** attribute (the player-attribute name, e.g. `"Hunger"`), and +- contains a child **`GuiObject` named `Fill`** (the part the engine resizes/recolors). + +Optional per-bar attributes (each defaults from the stat's config, so usually you set only +`Stat`): `Max`, `Invert`, `WarnAt`, `FillColor`, `WarnColor`, `FillAxis` (`"X"` or `"Y"`). + +To add a bar: duplicate an existing one, change its `Stat` attribute. To remove one: delete it. +To re-skin: edit the `Fill` and surrounding elements however you like. (Bars tagged +`SurvivorStatBar` work too — handy for the upcoming Builder UI.) + +## How it ships (and always appears) + +You never have to wire the HUD up: + +1. **Rojo source / the demo** mount the `SurvivalHud` template into StarterGui directly. +2. **The drop-in `SurvivorCore.rbxm`** carries the templates inside the model; `start()` + installs them into StarterGui / StarterPlayerScripts / ReplicatedStorage if you haven't + supplied your own. +3. If somehow no HUD is present, the client builds a **minimal fallback** so a HUD always shows. + +Supply your own HUD (a ScreenGui in StarterGui with `Stat`-bearing bars) and the engine uses it +instead of installing the default. + +## Editing the bundled template (contributors) + +The template is version-controlled as JSON (`assets/hud/SurvivalHud.model.json`, +`assets/config/SurvivalStatsConfig.model.json`) and mounted by Rojo. **Rojo syncs files → Studio, +not back** — so if you restyle the template live during a `rojo serve` session, save your changes +back to the `.model.json` (or re-author and overwrite it); a plain Studio edit under an active +sync is overwritten on the next sync. For your *own game*, just edit the `SurvivalHud` ScreenGui +in your place — it persists there normally. + +--- + +See also: [Extending SurvivorCore](extending.md) · [Architecture](architecture.md).