{"id":"zenukopublic/place-handoff","name":"place-handoff","scope":"zenukopublic","platform":"roblox","description":"Production-grade distributed cross-place session and party teleport handoff using MemoryStore with transactional rollback.","version":"1.0.0","latest":"1.0.0","versions":["1.0.0"],"license":"MIT","licenseRating":"safe","licenseCaveats":[],"licenseVerified":true,"dependencies":{},"integrity":"f13ac120f68ab9e68dc90189fd83d02b9af662e80f7ed5e6dab920e0bc98625e","likes":0,"downloads":0,"install":"forest install zenukopublic/place-handoff","url":"https://forest.dev/p/roblox/zenukopublic/place-handoff","files":"https://api.forest.dev/ai/package/roblox/zenukopublic/place-handoff/files","readme":"# `place-handoff`\n\n> **Production-grade distributed cross-place session and party teleport handoff using MemoryStore with transactional rollback.**\n\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![Language: Luau](https://img.shields.io/badge/Language-Luau-blue.svg)](https://luau-lang.org/)\n[![Wally: Available](https://img.shields.io/badge/Wally-Available-brightgreen.svg)](https://wally.run/)\n\n---\n\n> **Production Origin:** This package was extracted and generalized from infrastructure developed for **Kabbrawl**, a private competitive multiplayer Roblox experience. Kabbrawl itself remains proprietary; this package contains only reusable infrastructure and no proprietary assets or game-specific content.\n\n---\n## 🚀 The Multi-Place Problem on Roblox\n\nIn competitive Roblox games, separating your **Lobby place** from dedicated **Match arena places** (using reserved servers) is essential for server performance and clean memory. However, developers face three notorious production problems:\n1. **\"Session Locked\" DataStore Crashes:** ProfileStore locks player profiles in the lobby. If a player teleports to a match server before the lobby confirms the profile save, the match server fails to acquire the profile lock and kicks the player.\n2. **TeleportData Spoofing:** Exploiters can easily manipulate client-side `TeleportData` to fake their MMR, inventory, or tournament bracket slot.\n3. **Partial Party Teleport Failures:** If one player in a 4-player party fails to allocate handoff data, partial matches start with missing teammates.\n\n**`place-handoff`** provides an enterprise-grade, distributed handoff protocol backed by `MemoryStoreService` HashMaps with automatic TTL expiration, single-use token consumption, and transactional rollback.\n\n---\n\n## ✨ Features\n\n* **Single-Use Cryptographic GUIDs:** Generates secure handoff tokens with automatic 60-second TTL.\n* **Instant Replay-Attack Prevention:** Destination servers immediately delete the token from MemoryStore upon arrival (`RemoveAsync`).\n* **Transactional Rollback:** When teleporting parties, if any member fails to write to MemoryStore, all previously allocated party tokens are deleted instantly.\n* **Zero Session Lock Race Conditions:** Seamlessly pairs with profile saving (`PlayerProfile.waitForConfirmedSave`) before teleport.\n* **Mockable Adapter:** Easily test cross-server handoffs headlessly with Lune or in Studio.\n\n---\n\n## 🚀 Installation\n\n### Via Wally\nAdd `place-handoff` to your `wally.toml`:\n\n```toml\n[dependencies]\nPlaceHandoff = \"zenukopublic/place-handoff@1.0.0\"\n```\n\n---\n\n## 📖 Quickstart\n\n### 1. Lobby Server (Before Teleport)\n\n```luau\nlocal PlaceHandoff = require(Packages.PlaceHandoff)\nlocal handoff = PlaceHandoff.new()\n\n-- Teleporting a single player or party\nlocal partyResult = handoff:createPartyHandoff({ player1.UserId, player2.UserId }, function(userId)\n    return {\n        matchMode = \"Ranked\",\n        mmr = 1750,\n        teamSlot = \"Alpha\",\n    }\nend)\n\nif partyResult.success then\n    -- Pack GUID into TeleportOptions and teleport\n    local teleportOptions = Instance.new(\"TeleportOptions\")\n    teleportOptions:SetTeleportData({\n        handoffGuid = partyResult.guids[player1.UserId],\n    })\n    TeleportService:TeleportAsync(MATCH_PLACE_ID, { player1 }, teleportOptions)\nelse\n    warn(\"Failed to create match handoff:\", partyResult.error)\nend\n```\n\n### 2. Match Server (Upon Player Arrival)\n\n```luau\nlocal PlaceHandoff = require(Packages.PlaceHandoff)\nlocal handoff = PlaceHandoff.new()\n\nPlayers.PlayerAdded:Connect(function(player)\n    local joinData = player:GetJoinData()\n    local teleportData = joinData.TeleportData\n    local guid = if teleportData then teleportData.handoffGuid else nil\n\n    -- Validate identity and consume single-use token\n    local ok, payload, err = handoff:consumeHandoff(player.UserId, guid)\n    if not ok then\n        player:Kick(`Match admission denied: {err}`)\n        return\n    end\n\n    print(`Admitted {player.Name} to match with MMR {payload.mmr}!`)\nend)\n```\n\n---\n\n## 🧪 Testing\n\nRun standalone unit tests headlessly with [Lune](https://github.com/lune-org/lune):\n```bash\nlune run tests/handoff.test.luau\n```\n\n---\n\n## 📄 License\n\nMIT License. Free for personal and commercial use.\n","readmeTruncated":false}