Files
SurvivorCore/CONTRIBUTING.md
T
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

12 KiB

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.

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 or via rojo plugin install.
  • Rokit — the toolchain manager. It installs the exact pinned versions of rojo, wally, stylua, selene, and luau-lsp from rokit.toml.
  • Wally — 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:

    git clone https://github.com/<you>/SurvivorCore.git
    cd SurvivorCore
    
  2. Install the toolchain (reads rokit.toml):

    rokit install
    
  3. Install packages (no-op until wally.toml gains dependencies, but get in the habit):

    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:

    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). 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.
  • 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 devmain) 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 mainretarget 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 basedev for code, main for docs-only:
    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:
    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:
    git commit -m "Add Mobs registry kill-event schema"
    
  6. Push and open a pull request against dev (or main for documentation-only changes):
    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 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, 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. See 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 and the companion Extending SurvivorCore guide. The engine/content boundary map (which systems are being extracted from the production game The Counter Earth 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.