Files
Samuel LisonandClaude Opus 4.8 29bd20a93f feat(builder): schema-driven Build page for world objects (#11)
Select a Part or Model in Studio, answer "what is this object?", fill a form —
it becomes a gatherable node, a mob or a quest giver. Closes the gap between
authoring a def and setting up a world object, which until now meant knowing to
tag a part and hand-typing PascalCase attributes in the property panel.

Engine — components can declare an attribute SCHEMA:
- src/components/Schema.luau (new): AttributeSpec/Display/ComponentSchema types,
  normalize/defaults/get/list, and the schemas for Gatherable, Mob, QuestGiver.
  Dependency-free ON PURPOSE: the plugin requires it live at edit time, and the
  component modules themselves can't be required there (Harvesting asserts
  IsServer; Remotes creates instances in ReplicatedStorage).
- Components.define now accepts EITHER the legacy `attr = default` map or a
  schema array, normalizing both to one ordered spec list; bind() reads the
  derived default map, so binding is byte-identical. Legacy maps are sorted, as
  `pairs` order is arbitrary and would make a UI jitter. New getSchema/
  listSchemas. The three shipped components pull name/tag/display/attributes
  from the schema; their onSetup bodies are untouched (defaults verified
  identical, all 23 attributes).

Plugin — the Build page:
- Field.luau (new): coerce/format/equalsDefault, lifted from ConfigAdmin (which
  now delegates), shared by every schema-driven editor.
- FieldRow.luau (new): the shared [○/●] label … control + help row, including a
  ⌄ picker that cycles authored ids for fields declaring `ref`.
- BuildAdmin.luau (new): live schema read with three distinct empty states,
  selection/eligibility/identify, deltas-only attribute writes, applyType
  (tag + clear any other component) and clear.
- BuildAdminUi.luau (new): chooser cards, grouped form, multi-select apply,
  stale-bind-marker warning, SelectionChanged-driven refresh.
- init.server.luau: record()-wrapped buildActions + the page.

Docs: docs/admin-plugin.md Build section + a 60-second walkthrough,
docs/extending.md schema guide, a CONTRIBUTING rule that new creator components
declare one, CHANGELOG.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-30 17:14:52 +10:00

221 lines
12 KiB
Markdown

# Contributing to SurvivorCore
Thanks for your interest in contributing to SurvivorCore! This guide will help you get set up
and understand how the project is organized.
SurvivorCore is an **engine of mechanics, not content**. Most of what makes a survival game
*yours* — items, world, art, lore — lives in your own game and plugs in through the engine's
two extension layers. Contributions to this repo are about the *engine*: registries,
components, hooks, and the foundation. See [Architecture Overview](#architecture-overview).
## Prerequisites
- **Roblox Studio** — to serve the project into and play-test.
- **The Rojo Studio plugin, version `7.6.1`** — pin it to match the CLI the project uses (see
`rokit.toml`). A mismatched plugin can fail to sync. Install from the
[Rojo plugin page](https://create.roblox.com/store/asset/13916111004/Rojo) or via
`rojo plugin install`.
- **[Rokit](https://github.com/rojo-rbx/rokit)** — the toolchain manager. It installs the
exact pinned versions of `rojo`, `wally`, `stylua`, `selene`, and `luau-lsp` from
`rokit.toml`.
- **[Wally](https://wally.run)** — the Roblox package manager (installed by Rokit). The engine
ships with no dependencies today, but `wally install` keeps you forward-compatible.
- **Git** — for cloning and version control.
## Local Development Setup
1. **Fork & clone the repo:**
```bash
git clone https://github.com/<you>/SurvivorCore.git
cd SurvivorCore
```
2. **Install the toolchain** (reads `rokit.toml`):
```bash
rokit install
```
3. **Install packages** (no-op until `wally.toml` gains dependencies, but get in the habit):
```bash
wally install
```
4. **Serve into Studio.** Open a place in Studio (an empty baseplate is fine), make sure the
Rojo plugin is connected, then in your terminal:
```bash
rojo serve demo.project.json
```
Click **Connect** in the Rojo plugin. This mounts the engine at
`ReplicatedStorage.SurvivorCore` and the demo boot script in `ServerScriptService`.
5. **Run the demo place.** Press **Play**. The demo registers a couple of items and a recipe,
then watches for creator-owned `Gatherable` objects. To try the component layer: add a
`Part` to the Workspace, tag it `Gatherable` (CollectionService), set the attributes
`ItemId="reed"`, `Yield=2`, `HP=3`, then play and interact with the prompt.
> Working on the engine alone (not the demo)? `rojo serve default.project.json` mounts just
> `src` as the `SurvivorCore` model.
> **Studio + Rojo gotcha — restart before trusting a Play test.** When you change scripts during a
> `rojo serve` session, Studio updates each script's `Source` in the Edit datamodel, but **Play
> Solo can run cached old bytecode** — so your change silently doesn't take effect on Play. If a
> fix isn't showing up, **restart Studio** (clears the script cache) and reconnect, or test from a
> fresh build: `rojo build demo.project.json -o /tmp/demo.rbxlx` and open that file.
## Code Style
- **Luau, typed where practical.** Use `--!strict` on new modules when the types are clean;
fall back to the project default otherwise. Prefer `export type` for public shapes.
- **Formatting & linting are enforced by CI.** Before you push, run the same checks CI does
(see [Making a Pull Request](#making-a-pull-request)). `stylua` owns formatting — don't
hand-format around it.
- **Default art is fine; keep it swappable.** Assets in this repo are free to use, so the engine
may ship free *default* art IDs (e.g. the HUD stat icons in `StatDefs`) — provided they stay
overridable (config / admin plugin / `Assets`) and never get buried in logic. For dynamic or
owner-supplied art, still route through the `Assets` registry
(`Assets.register("Sounds", "Harvest", "rbxassetid://…")`) with the empty-string fallback.
- **Keep the core free of game-specific *design*.** No concrete items, recipes, lore, world
strings, or instance-name string matches (`name == "campfire"`) in the engine — free default
*art* is the one exception (above). Content enters two ways:
- **Registries** — developers call `register()` from code (`Items`, `Recipes`, `Stats`,
`Mobs`, …).
- **Components** — creators tag their own objects and set Attributes (`Gatherable`, and the
component family that follows). **A new creator-facing component must declare an attribute
schema** (a `Schema.COMPONENTS` entry in `src/components/Schema.luau`, plus a `display` block),
so the admin plugin's **Build** page can render its setup form — no creator should have to
memorise attribute names. See [docs/extending.md](docs/extending.md#declaring-a-schema-so-the-builder-can-render-a-form).
- **Extend via Hooks, don't fork.** Game-specific flourish (felling physics, station VFX,
custom drops) belongs in a `Hooks.on(...)` handler in the *game*, not baked into the engine.
If you need a new extension point, add a `Hooks.run("…")` call and document it.
- **Mind the two layers.** Engine code lives in `src/`; `demo/` is an example consumer and may
use concrete content freely (it stands in for a game).
## Branching model
SurvivorCore uses two long-lived branches:
- **`dev`** — the active development / integration branch. **All code changes land here.**
- **`main`** — the stable, released branch. It only moves when maintainers cut a release (by
merging `dev` → `main`) or for **documentation-only** changes.
**In short: code → `dev`, docs → `main`.**
- **Code work** (anything under `src/`, `demo/`, workflows, toolchain/build config): fork,
branch from **`dev`**, and open your PR against **`dev`**.
- **Documentation only** (`README`, `docs/`, `CONTRIBUTING.md`, code comments, typos): you may
branch from **`main`** and PR against **`main`** — apply the `skip-changelog` label.
Use branch prefixes: `feat/*`, `fix/*`, `docs/*` (e.g. `feat/mob-registry`).
`main` is the default branch, so a fresh PR targets `main` — **retarget code PRs to `dev`.** A
code PR left on `main` will be asked to retarget. Releases are cut by maintainers: `dev` is
merged into `main` (a **merge commit**, not a squash, so the branches stay in sync),
`## Unreleased` is promoted to the new version, and `main` is tagged + a GitHub Release is
published (which auto-posts to Discussions → Announcements).
## Making a Pull Request
1. **Fork** the repository on GitHub.
2. **Create a branch from the right base** — `dev` for code, `main` for docs-only:
```bash
git checkout -b feat/my-feature dev # code work
# git checkout -b docs/my-fix main # documentation only
```
3. **Implement your change.** Write clear, typed Luau. Add comments where the "why" isn't
obvious. Keep the engine free of game-specific design (free default art is fine).
4. **Test locally.** Play-test in Studio, then run the same checks CI does:
```bash
stylua --check src demo assets plugin
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 assets/client
# the admin plugin is a separate Rojo tree — analyze it with its own sourcemap
rojo sourcemap plugin.project.json --output plugin-sourcemap.json
luau-lsp analyze --sourcemap plugin-sourcemap.json --defs globalTypes.d.luau --no-strict-dm-types plugin
rojo build default.project.json --output SurvivorCore.rbxm
rojo build demo.project.json --output demo.rbxl
rojo build plugin.project.json --output SurvivorCoreStatAdmin.rbxm
```
(`stylua src demo` auto-formats; `sourcemap.json`, `globalTypes.d.luau`, and the build
outputs are git-ignored.)
5. **Commit** with a clear message describing what the change does and why:
```bash
git commit -m "Add Mobs registry kill-event schema"
```
6. **Push** and open a pull request against **`dev`** (or `main` for documentation-only
changes):
```bash
git push origin feat/my-feature
```
7. In the PR description, explain what the change does, why it's needed, and how to test it in
Studio. Add a clip/screenshot for anything visible.
**Changelog (required).** Every pull request must add an entry to [`CHANGELOG.md`](CHANGELOG.md)
under the `## Unreleased` heading (create it if missing), grouped under
`### Added` / `### Changed` / `### Fixed` / `### Security`, with `(#N)` referencing the issue or
PR. CI enforces this. If a change genuinely warrants no entry (a docs-only PR, a CI-config
tweak, a typo fix), apply the `skip-changelog` label to bypass the check. At release time,
`## Unreleased` is renamed to the new version.
**Tracking staged fixes (`fixed-pending-merge`).** When a PR implements the fix for an open
issue or a security alert, maintainers label it `fixed-pending-merge` (and the issue it
closes), so it's easy to see at a glance which problems are fixed and just waiting on a merge.
The label needs no cleanup: on merge the PR closes and any linked issue auto-closes via a
`Closes #N` reference.
**Closing multiple issues from one PR.** Give each issue its own keyword —
`Closes #1, Closes #2` — not `Closes #1, #2`. GitHub only auto-links the number that directly
follows a closing keyword, so the bare `#2` in the second form won't auto-close.
## Issue Templates
File issues with the forms in `.github/ISSUE_TEMPLATE/`:
- **Bug report** — include a clear repro (which project to serve and what to do in Studio),
expected behavior, the SurvivorCore version, your Roblox Studio version, your Rojo plugin
version, how you consume the engine (Rojo / Wally / drop-in `.rbxm`), and any Output errors.
- **Feature request** — describe a reusable *engine* capability (a registry, component, hook,
or config section), the use case, and which layer it touches. Game-specific content belongs
in your own game, not the engine.
Open-ended questions and "how do I…?" go to
[Discussions](https://github.com/TemujinCalidius/SurvivorCore/discussions), not Issues.
## Reporting a security issue
Found a vulnerability in the engine? **Don't open a public issue, PR, or discussion** — that
discloses it to every game built on SurvivorCore before a fix exists. Report it privately via
[**Security → Report a vulnerability**](https://github.com/TemujinCalidius/SurvivorCore/security/advisories/new).
See [`SECURITY.md`](SECURITY.md) for the full coordinated-disclosure policy, supported versions, and scope.
## Architecture Overview
SurvivorCore exposes two extension layers over a small foundation:
- **Foundation** (`src/foundation/`) — `Config`, `Assets`, `EventBridge`, `Hooks`, `Registry`.
- **Registry layer** (`src/registries/`) — empty registries the game populates at startup
(`Items`, `Recipes`, `Stats`, `Achievements`, `Codex`, `Appearance`, `Mobs`).
- **Component layer** (`src/components/`) — behaviors bound to a CollectionService tag and
configured by per-instance Attributes (`Gatherable`, …).
For the full picture, read [docs/architecture.md](docs/architecture.md) and the companion
[Extending SurvivorCore](docs/extending.md) guide. The engine/content **boundary map** (which
systems are being extracted from the production game [The Counter
Earth](https://github.com/TemujinCalidius/TheCounterEarth) into this engine) lives in that
game's repo at `docs/survivorcore-boundary-map.md`.
## Code of Conduct
SurvivorCore is a small project, and we want to keep the community welcoming for everyone.
- **Be kind.** Assume good intent. Disagree respectfully.
- **Be inclusive.** Welcome newcomers. Avoid jargon without explanation.
- **Be constructive.** When reviewing code, suggest improvements rather than just pointing out
problems. Explain why.
- **No harassment, discrimination, or personal attacks.** This includes issues, PRs,
Discussions, and any project communication channels.
If someone's behavior makes you uncomfortable, reach out to the maintainers. We will address it.