The bow release handler enforced no cooldown — the only client-driven action
in the engine without one (melee, harvesting and item use all rate-limit). Each
release also costs up to MaxRange/StepSize server raycasts to simulate the arc,
so the gate now runs BEFORE the arrow is spent and before the simulation.
- Combat.Bow.Cooldown (0.35s) added to CombatConfig + the EngineConfig schema,
so it's tunable no-code in SurvivorCore Studio.
- onBowRelease honours def.weaponCooldown first, falling back to Bow.Cooldown.
weaponCooldown was previously read only by the melee path, even though the
authoring form offers it for every weapon — a bow cooldown was silently
ignored. Its plugin label is now "Cooldown (s)" noting it covers both.
- A release with no matching draw is rejected (a real client fires BowDraw on
press, BowRelease on release).
- BowDraw validates the sender is alive and holding a bow; it previously
accepted anything from anyone.
- Aim points are checked for finiteness: a non-finite Vector3 defeats magnitude
comparisons and would reach the raycast after the arrow was already spent.
- lastShot is cleared in PlayerRemoving alongside lastSwing/drawStart.
Affects v0.8.0 and earlier.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two UX bugs found while recording:
1. The trade window relied on the SEPARATE inventory menu for staging, and
clicking the menu's backdrop closed it with no obvious way to reopen —
leaving the trade unusable. The window now lists your carried stacks
INSIDE it (read from the replicated inventory attributes): click a row to
offer one, "All" for the whole stack. Dragging from the inventory grid
still works when that menu happens to be open. Removed the auto-open of
the inventory panel that created the trap.
2. The window couldn't be moved. Its header is now a drag handle (mouse +
touch), so it can be pulled out of the way.
Also: the in-window backpack live-refreshes on inventory attribute changes
(coalesced per frame) so a mid-trade pickup can't leave it stale. Panel grown
to 420x476 for the added section.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replaces the buggy per-character Trade ProximityPrompt — which rendered over
your OWN head (no per-viewer exclusion) — with a TCE-ported interact window.
Walk up to another player → an "[E] Interact" badge floats over THEIR head →
press E / tap → a window shows their name + survival stats and an action list.
Targeting is a client-side nearest-OTHER-player scan (self skipped in the loop,
with hysteresis to stop flicker), so the affordance can never point at you —
the reported bug is fixed by construction.
Extensible: SurvivorCore.Interact.addAction{ id, label, order?, enabled?,
onActivate } — Trade is registered as the first built-in action and just fires
the existing (server-validated) TradeRequest remote, so the invite/Accept flow
is unchanged. Cursor is freed while the window is open; the window auto-closes
on walk-away / target-leaves / death / trade-start.
- src/client/PlayerInteract.luau (new): scan + billboard + window + action registry
- src/systems/Trade.luau: delete the per-character prompt block (keep Died abort)
- src/init.luau: boot PlayerInteract, expose SurvivorCore.Interact
- src/shared/UiConfig.luau: UI.Keybinds.Interact = "E"
- docs/interact.md (new), docs/trading.md, CHANGELOG (Unreleased)
Ported from The Counter Earth's PlayerInspectService/InspectController.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A new server-authoritative, dupe-proof trade system. Walk up to another player,
trigger the "Trade" prompt; they Accept/Decline; both stage loose backpack
stacks (drag from the inventory grid, with −/+ qty steppers) and must Confirm
before anything moves.
Anti-dupe by construction: staging is by-reference (items never leave the owner
until commit), and the commit is one synchronous, no-yield critical section —
re-validate holds → pre-flight both receivers have room (new Inventory.canAccept)
→ remove both → grant with addUpTo → refund any residue. Item count is conserved
on every branch (verified: 200k-iteration conservation + fit-oracle fuzz).
Auto-cancels on death / leave / out-of-range (MaxDistance) / request timeout; a
staging change resets both confirms. New Trade server system + TradeUi client
window, the "Trading" Config section (tunable in SurvivorCore Studio), hooks
trade:started / trade:completed, and a trades_total progression counter. v1 is
backpack stacks only (worn gear reserved behind AllowEquippedItems).
- src/systems/Trade.luau, src/client/TradeUi.luau, src/shared/TradingConfig.luau (new)
- src/systems/Inventory.luau: exported canAccept (weight+slot fit oracle)
- src/shared/EngineConfig.luau: Trading section; src/init.luau boot wiring
- docs/trading.md, README, CHANGELOG (Unreleased), Hooks catalogue, extending.md
- demo/server/TradeTestStation.server.luau: /tradetest 2-player conservation harness
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Promote CHANGELOG Unreleased → 0.8.0 (SurvivorCore Studio, Engine Config #21,
content overrides #40). Bump wally.toml + SurvivorCore.VERSION to 0.8.0.
Collateral: admin-panel demo video tile on the landing site + docs/admin-plugin
Demos line; refresh the admin feature card + value-prop to name SurvivorCore
Studio / Engine Config / content overrides; bump version surfaces across
site, README and getting-started.
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>
Dying again mid corpse-run drops a second bag (old + new coexist in the world,
each on its own 5-minute timer) and re-points the owner's beacon at the new
one. Previously the OLDER bag's later despawn/looting fired the unscoped
beacon-clear and wiped the beam pointing at the newer bag. LootBags now tracks
each owner's latest bag and only its destruction clears the beacon.
docs/loot-bags.md: documented the multi-bag + beacon-follows-newest behavior.
Live-verified: two coexisting bags, beacon on the newest; looting the OLD bag
left the beacon untouched; looting the NEW bag cleared it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Root cause: with StreamingEnabled (the new-place default), respawning away
from the death spot let Roblox evict the bag from the owner's client — the
bag looked deleted, and the local Destroying event fired, permanently killing
the beacon (server bag was fine all along).
- The bag is now a Model set PersistentPerPlayer for its owner
(AddPersistentPlayer) — it never streams out of the owner's view.
- The beacon is position-driven plain data: no Instance in the remote, so no
replication race (the 0.5s delay hack is gone) and no streaming fragility.
- The beam clears on an explicit server nil-fire when the bag is truly
emptied/expired (plus a lifetime fallback), not on replica Destroying.
Live-verified (fresh bytecode confirmed via bag ClassName): beacon appears
instantly, survives respawn, bag stays visible to the owner, loot restores
everything, beam clears on empty.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Beacon: LootBagDropped fired the same frame the bag was created, so the
Instance reference arrived as nil on the client (not yet replicated) and the
owner beacon silently never appeared. The fire is now delayed 0.5s.
- Dead looting: collect() never verified the collector was alive, so a corpse
lying next to its own bag could scoop everything back before respawning.
The server now rejects dead collectors (isAlive gate, Harvesting pattern).
Live verification note: the running Studio session executed stale bytecode
(old collect granted loot to a dead player even though the new source was
synced) — verified source-in-place; behavior verification needs a Studio
restart, which CI/release artifacts are unaffected by.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- ToolEquip: possession gate — a pin is only a pointer; never equip an item
the player doesn't carry (a stale/seeded pin without a backing stack could
conjure a usable ghost Tool).
- Hotbar UI: dim a pinned icon to 0.45 (the Crafting-tab convention) when the
player owns none of the item, so an inert pin reads as inert.
- Demo: husks spawn beyond aggro+wander reach of the spawn point — no more
farming fresh players before they pick up a weapon.
Verified live in Studio: a war_bow pin with zero carried refuses to equip and
renders dimmed; owned pins equip normally at full opacity.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Promote CHANGELOG Unreleased → 0.7.0 and bump wally.toml. (VERSION already
0.7.0 in src/init.luau.)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Hunting & butchering (#13): mob defs gain flat carcass fields (carcassItem/Hp/
Tool/YieldMin/Max/Seconds); a slain mob leaves a butcherable CARCASS that is a
tagged Gatherable — the whole harvesting pipeline (tool gate, per-hit yields,
HP bar, gather:* hooks, progression counters) is reused as the butcher flow.
Carcass looks come from SurvivorCoreContent.Carcasses.<mobType>; reactions key
on "<mobType>_carcass"; fields editable in the admin Mobs editor. Gatherable
gains generic PromptText/PromptObject attributes.
Death loot bags (#19, TCE port): dying drops inventory AND worn equipment
(config-toggleable) into an anchored ground-clamped bag — IntValue contents,
instant-loot prompt for anyone, floating countdown, owner-only beacon, death
toast, LifetimeSeconds despawn. Pickup is loss-proof: equipment restores to
empty slots first (satchel re-grows capacity before stacks), the rest grants
up-to-fit and the remainder stays bagged. New player:died lifecycle → deaths
counters via Progression; lootbag:dropped/collected hooks; TCE respawn camera
fix. New Inventory APIs: getEquipment, clearAll (capacity-safe order), addUpTo,
restoreEquip.
Fixed: ToolEquip forces CanBeDropped=false on cloned creator templates.
Demo: stone_knife + raw_meat (risky raw: Hunger -25, Poison +8), boar carcass,
multi-objective hunt_boar quest, hunter/butcher/hard_way achievements. Docs:
mobs.md butchering section + loot-bags.md; CHANGELOG; site card copy.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Wire the demo video (makertube uSGJ2MHEFjSSKxMiJBJ6Y5) into quests.md,
achievements.md, content-authoring.md and admin-plugin.md, and feature it in
the landing page's video grid (replacing the oldest embed; that video stays
linked from docs/inventory.md).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>