Found in real use: with no gatherables authored yet, the Build form's Resource
field has no ⌄ picker (there's nothing to cycle) and nothing explains where
entries come from — so the very first no-code run dead-ends at the moment the
flow should feel best.
A field that references authored content now shows "No Gatherables yet —
create one →" when that category is empty, which jumps straight to the right
Content page. Not a mutation, so it isn't wrapped in a ChangeHistory recording.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Found by an adversarial review of the Build page before it was ever run — all
three reviewers independently flagged the first one.
1. The "what is this object?" cards were dead. chooserCard parented a
Size=fromScale(1,1) TextButton into Theme.panel() intending an overlay, but
Theme.panel() contains a UIListLayout, which lays out EVERY GuiObject child —
there is no opt-out, and ZIndex does not affect layout. So the button became
another list row: clicking a card's title/summary did nothing, and the
oversized button spilled past the card and took the click for the card BELOW,
applying the WRONG component (which also strips the previous component's
attributes). This was the page's primary interaction.
Fixed with Theme.panelButton() — the card itself is the button, matching the
pattern OverviewPage already uses.
2. Clear did nothing on an object whose only tag was unknown to this engine,
yet reported success. The page renders exactly that branch with a Clear
button. BuildAdmin.clear now takes the tags to remove explicitly, and the page
passes the unknown tags it just displayed — never removing an unlisted tag
speculatively, since it may belong to another plugin. It also reports
"nothing to clear" instead of a false success.
3. FieldRow's help text set Position inside a UIListLayout parent, so its indent
was silently dropped and every help line rendered flush left. Uses padding now.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
Engine: quest overrides with objective*/reward* fields on nested code quests
now WARN at boot (they merge nothing — QuestData prefers nested tables);
EngineConfig + ConfigAdmin reject non-finite numbers (inf/nan).
Plugin: Config group panels auto-size (fixed-height math clipped the last row
of big groups — Theme group lost its Bold font row); Stats panels get the gap
math right; the Engine Config explainer page now REBUILDS the window when the
engine appears (the old hint was impossible — pages were assembled once at
plugin load); create() refuses ids that already have an override (mirror
guard); failed Create/Override reasons render under the create row; rejected
config edits report in the footer; explicit navigation cancels a pending
debounced search jump; the mount-time page restore no longer clobbers the
saved last-page setting during an engine-less session; undo/redo refresh
skips while typing in one of the plugin's own text boxes; search results past
the 50-cap no longer render empty category headers.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Quests (#10): a Quests registry + server runtime — objectives (gather/craft/
kill/use × count, blank target = any) and rewards, autoStart + requires chains
(completing a prerequisite auto-starts dependents), optional turn-in at a
tagged QuestGiver (component + prompt). Rewards are never lost: a full
inventory parks the quest as ready and the grant retries on inventory change.
Quests menu tab (L) with per-objective progress bars; QuestLog JSON attribute
replicates state; quest:started/progress/completed/blocked via Hooks + bus.
Achievements: an always-on runtime for the existing registry, ported
architecturally from TCE. A shared Progression layer translates bus events
into auto-derived counters (gathers_reed, crafts_total, kills_husk, …) so a
def is just { key, name, counter, threshold } — flat in code and no-code.
Unlock-once + toast + Achievements tab (J) with progress bars.
Toasts: themed top-right notification queue (Notify remote + Toasts.show).
No-code: admin plugin gains Quests (single-objective + "+ Quest giver" drop)
and Achievements (counter/threshold) editors; the engine loads both from
SurvivorCoreContent. Demo: a 3-quest chain + 4 achievements + a giver post.
Fixed: registerPanel now ADOPTS a template-scaffolded tab (hides the
placeholder, builds into the authored frame) instead of silently no-opping —
this replaces the Quests/Achievements "coming soon" placeholders.
EventBridge parity: gather:*, craft:* and item:use now also cross the bus.
Changed: rojo 7.6.1 → 7.7.0 (rokit pin; CI follows; gate verified).
Docs: quests.md + achievements.md (new), content-authoring/admin-plugin/
extending/README updated, CHANGELOG Unreleased, site feature card.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Mob & AI engine (#17): a `Mob` component + a `Mobs` FSM runtime
(idle/wander/chase/attack/flee/leash/death), faction-driven profiles
(hostile/passive/neutral), Humanoid-based mobs so combat/health/death share one
path, per-mob-type reactions, and a `Mobs` Config section.
Combat (#12, #14): server-authoritative melee + ranged reusing the v0.4.0 swing
pipeline; the `combat:hit`/`combat:kill` kill-event schema designed once. Bows use
a TCE-style aim (hold RMB to aim with an over-the-shoulder camera + crosshair and
charge ring, hold LMB to draw, release to fire); the server simulates the gravity
arc and sends the path so the client flies a cosmetic arrow along the real curve.
Arrows are their own configurable ammo item (weight/damage/curve/range/speed).
No-code: admin plugin gains Mob, Weapon and Arrow editors (+ "Add to World" /
"Tool model" drops); the engine loads Weapons/Arrows/Mobs from SurvivorCoreContent.
Fix: scope the orphan hotbar-pin sweep to the removed/consumed item, so consuming
one item never clears an unrelated hotbar-pinned weapon.
Docs: combat.md + mobs.md (with demo video); CHANGELOG Unreleased entry.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Closes the gather → craft loop and lays the no-code content layer.
- Tool-swing harvesting (#1): equip a hotbar tool (a real Tool via the new
ToolEquip bridge), click a node, server-validated hit (range / line-of-sight
/ equipped tool / cooldown) granting a per-hit random yield, blocking the hit
when the inventory is full. Bare-hand hold-E kept. The engine's first
client-input → RemoteEvent → server-validation pipeline (combat reuses it).
A floating HP bar shows a node's remaining hits.
- Hand crafting (#4): server-authoritative consume → produce with a
complexity-scaled craft channel + a progress bar above the crafter; a
Crafting tab lists hand recipes and gates each on what you carry.
- No-code content (#11, Builder first slice): a Resources registry + a
content-from-instances loader (Registry.loadFromFolder reads a
SurvivorCoreContent folder at start), Gatherable binds to a named Resource,
and a per-resource-type reaction API (reed sways, tree fells + leaves a
stump). The admin plugin becomes one widget with Stats + Content tabs to
create/edit items + gatherables, including "Add to World".
New hooks: gather:blocked, craft:start / craft:end / craft:blocked. Docs +
CHANGELOG. Engine stays content-free; the demo supplies the sample content.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Studio doesn't run the HUD client binder in Edit view, so an authored SurvivalHud
shows its static template there — full bars, blank readouts, and only the shipped
default icons (an icon override isn't visible until Play). The admin plugin now has
a footer with **Preview HUD** / **Clear** that paints, in Edit, what the running
game would render, so owners tune-and-see without pressing Play.
- plugin/HudPreview.luau (new): mirrors the engine binder's resolution — per-bar
`Icon` attribute › the stat's effective icon (config/admin override else shipped
default) for icons; FillAxis-aware sample fill; per-stat ValueFormat for the
readout; a sample counter value. Every property is snapshotted once before the
first write and clear() restores them exactly (so it's fully reversible);
apply() takes an optional hud root for testability. Skips the Assets registry
tier (runtime-only, empty in Edit) and never guesses engine-owned invert/dangerHigh.
- StatAdminUi: a footer bar with Preview / Clear buttons + a status readout.
- init.server: wires both through ChangeHistory (each is one undo step).
- Verified in Studio: synthetic-HUD logic test (icon resolution, sample fill/value,
exact restore) + a live visual pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A Studio dock widget that lets the experience owner tune the survival stats
through a validated form instead of hand-editing Attributes on the
SurvivalStatsConfig instance — the first slice of the Builder/Admin plugin (#11).
The point is compatibility: edits are LOCKED against engine updates. StatAdmin
(the pure, headlessly-testable logic layer) writes deltas only — it sets an
attribute solely when the owner changes a field from the live engine default,
and removes it on reset / edit-back-to-default. So unset fields keep following
the (improvable) engine defaults across a SurvivorCore release, while explicit
overrides live on the owner's instance, which the engine only ever seeds and
never overwrites. Nothing tuned is lost; nothing left alone is frozen.
Hard guardrail: the plugin can read/write only the seven owner-tunable fields
(STUDIO_ATTR_MAP). A write() assert makes it impossible to ever set the
engine-owned semantics Invert / DangerHigh — re-freezing the affliction
fill-direction bug is structurally unreachable. Runtime-verified in Studio:
every write path exercised (including rejected Invert/DangerHigh attempts) left
zero banned attributes on the instance.
- plugin/StatAdmin.luau — logic: roster, effective values, deltas-only writes
- plugin/StatAdminUi.luau — the dock-widget form (per-field reset, override dots)
- plugin/init.server.luau — toolbar/widget wiring + ChangeHistory undo steps
- plugin.project.json — separate Rojo tree; build with --plugin to install
- CI: stylua + a plugin-sourcemap luau-lsp pass + a plugin build
- docs/admin-plugin.md + cross-links; fix stale Invert row in the no-code table
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>