Summary
Allow script packages to include client-side Lua scripts and client assets.
The server sends the current package revision to clients when they join and distributes new revisions during hot reload without requiring a server restart.
Client scripts run in isolated, package-scoped runtimes and expose a dedicated client API. This proposal provides the foundation for future client-side UI, input, rendering, and game-client APIs.
Related proposals:
Package Layout
example_package/
package.json
server/
main.lua
client/
main.lua
shop.lua
assets/
shop.rml
shop.rcss
The existing package manifest proposal should be extended rather than replaced:
{
"id": "example_package",
"version": "1.0.0",
"entry": "server/main.lua",
"dependencies": {},
"permissions": [
"server.events.register",
"client",
"client.ui",
"network.channel.send",
"network.channel.receive"
],
"client": {
"entry": "client/main.lua",
"resources": [
"client/**",
"assets/**"
],
"required": false
}
}
Existing top-level server package fields remain valid. The client section is additive.
Client Runtime
A client entrypoint receives:
local client = require "client"
local async = require "async"
local ref = require "ref"
local net = require "net"
The client runtime does not receive the server-side mc API by default.
The initial client API should provide:
client.on("start", function()
end)
client.on("stop", function(reason)
end)
client.on("tick", function(delta)
end)
client.log("Package started")
client.warn("Missing item")
client.resource("assets/shop.rml")
Client event behavior should follow the event proposal, including priorities, one-shot handlers, filters where applicable, and automatic event-group cleanup.
The client runtime should reuse the scheduler and async semantics from the existing proposals rather than create a separate incompatible scheduling system.
Package Revisions
Each client revision is identified by:
package ID + version + content hash
The content hash changes whenever a client script or declared client resource changes.
The client caches complete package revisions by content hash. A package must not activate from a mixture of files belonging to different revisions.
Join Loading
When a player joins:
- The server sends the current package catalog.
- The client compares package revision hashes with its cache.
- Missing revisions are downloaded.
- The complete bundle is verified.
- Dependencies are checked.
- A fresh isolated runtime is created.
- The client entrypoint is loaded.
- The package receives
start.
- The runtime becomes active.
The server resolves external package imports. The client does not fetch arbitrary GitHub URLs or other remote resources independently.
Optional package failure disables only that package. Required package failure must follow an explicit server policy: reject activation or prevent joining.
Hot Reload
Hot reload uses staged activation:
active revision A
-> download revision B
-> verify revision B
-> initialize revision B
-> activate revision B
-> stop revision A
The current revision remains active until the replacement has initialized successfully.
If initialization fails, the old revision remains active.
If revisions A, B, and C arrive while B is downloading, B is stale and must not activate. The client should converge on the newest revision, C.
A new revision gets a fresh runtime. This avoids stale require caches, old closures, old event handlers, and partially reset global state.
Package-local state is not preserved across reload by default. Persistent state must use an explicit system such as net.shared, net.channel, or a future client storage API.
Lifecycle and Cleanup
Stopping a package closes:
- Event subscriptions.
- Timers and scheduled tasks.
- Coroutines and async operations.
- Reactive watchers.
- Network handles.
- UI contexts introduced by dependent proposals.
- Resource references.
Every client package should have an implicit task and event group associated with its package revision. Reloading or stopping the package clears that group.
Shutdown must be bounded. A package must not be able to block a replacement revision indefinitely.
Networking
Networking remains defined by #13.
Client scripts may use:
local net = require "net"
local actions = net.channel("example_package:actions")
actions:send({
kind = "select",
value = "apple",
})
The client runtime supplies the package capability context, but does not add a second RPC system.
Old network handles close when the old runtime stops. The new runtime receives fresh handles and follows the normal pending -> ready -> closed lifecycle.
Transient channel messages sent to the old runtime are not replayed automatically. Persistent or recoverable state should use net.shared or an explicit channel snapshot protocol.
Capabilities
Capability permissions follow #12.
Suggested target-qualified capabilities:
client
client.ui
client.input
client.render
client.world.read
client.player.read
network.channel.send
network.channel.receive
Runtime checks must apply to calls made from:
- Event handlers.
- Timers.
- Watchers.
- Coroutines.
- Async continuations.
- UI callbacks.
A package with only client cannot access UI, input, world data, or networking unless those permissions are also granted.
This is capability control, not a perfect sandbox. A client cannot be protected from a malicious server that intentionally sends malicious code.
Dependencies
Client dependencies use the package dependency mechanism from #5. Dependencies must:
- Be present before activation.
- Load before dependents.
- Use compatible revisions.
- Reject dependency cycles.
Cross-package script, resource, and API access is not implicit and requires a future explicit capability.
Errors and Diagnostics
Client errors should be isolated to the package and reported with:
package ID
package revision
client/server side
script path
callback or event name
load state
reload state
stack trace
Useful package states include:
downloaded
verified
loading
active
stopping
failed
disabled
stale
Repeated or fatal errors may disable the package without affecting unrelated packages.
Configuration and Secrets
Client scripts may receive explicitly public client configuration, but server configuration and secrets must never be sent to the client.
Client scripts must not receive a client equivalent of the server secret API.
Non-Goals
- Defining the UI API.
- Defining client rendering APIs.
- Defining client world or player wrappers.
- Providing arbitrary filesystem access.
- Loading native libraries.
- Exposing Java or Minecraft client objects directly.
- Guaranteeing protection from a malicious server.
- Reusing the server API unchanged.
- Allowing server code to directly manipulate client UI.
Acceptance Criteria
Examples
Client Entrypoint
local client = require "client"
local net = require "net"
local actions
client.on("start", function()
client.log("Shop client started")
actions = net.channel("shops:actions")
end)
client.on("stop", function(reason)
client.log("Shop client stopped: " .. reason)
end)
client.on("tick", function(delta)
-- Optional per-tick client work.
end)
Package Resource
local client = require "client"
local document = client.resource("assets/shop.rml")
client.log("Using resource: " .. document)
Client resources are resolved within the package and cannot escape its resource namespace.
Hot Reload
A package revision can keep durable state on the server while client-local runtime state is rebuilt:
local net = require "net"
local state = net.shared("shops:state")
local actions = net.channel("shops:actions")
state:ready(function(shared)
-- Runs again when a new client revision reacquires the handle.
print(shared:get().title)
end)
actions.handler = function(message)
-- The handler belongs to this package revision and is removed on reload.
print(message.kind)
end
Optional Package Failure
A failure in an optional client package disables that package only. Other packages and the client remain active.
Summary
Allow script packages to include client-side Lua scripts and client assets.
The server sends the current package revision to clients when they join and distributes new revisions during hot reload without requiring a server restart.
Client scripts run in isolated, package-scoped runtimes and expose a dedicated client API. This proposal provides the foundation for future client-side UI, input, rendering, and game-client APIs.
Related proposals:
Package Layout
The existing package manifest proposal should be extended rather than replaced:
{ "id": "example_package", "version": "1.0.0", "entry": "server/main.lua", "dependencies": {}, "permissions": [ "server.events.register", "client", "client.ui", "network.channel.send", "network.channel.receive" ], "client": { "entry": "client/main.lua", "resources": [ "client/**", "assets/**" ], "required": false } }Existing top-level server package fields remain valid. The
clientsection is additive.Client Runtime
A client entrypoint receives:
The client runtime does not receive the server-side
mcAPI by default.The initial
clientAPI should provide:Client event behavior should follow the event proposal, including priorities, one-shot handlers, filters where applicable, and automatic event-group cleanup.
The client runtime should reuse the scheduler and async semantics from the existing proposals rather than create a separate incompatible scheduling system.
Package Revisions
Each client revision is identified by:
The content hash changes whenever a client script or declared client resource changes.
The client caches complete package revisions by content hash. A package must not activate from a mixture of files belonging to different revisions.
Join Loading
When a player joins:
start.The server resolves external package imports. The client does not fetch arbitrary GitHub URLs or other remote resources independently.
Optional package failure disables only that package. Required package failure must follow an explicit server policy: reject activation or prevent joining.
Hot Reload
Hot reload uses staged activation:
The current revision remains active until the replacement has initialized successfully.
If initialization fails, the old revision remains active.
If revisions A, B, and C arrive while B is downloading, B is stale and must not activate. The client should converge on the newest revision, C.
A new revision gets a fresh runtime. This avoids stale
requirecaches, old closures, old event handlers, and partially reset global state.Package-local state is not preserved across reload by default. Persistent state must use an explicit system such as
net.shared,net.channel, or a future client storage API.Lifecycle and Cleanup
Stopping a package closes:
Every client package should have an implicit task and event group associated with its package revision. Reloading or stopping the package clears that group.
Shutdown must be bounded. A package must not be able to block a replacement revision indefinitely.
Networking
Networking remains defined by #13.
Client scripts may use:
The client runtime supplies the package capability context, but does not add a second RPC system.
Old network handles close when the old runtime stops. The new runtime receives fresh handles and follows the normal
pending -> ready -> closedlifecycle.Transient channel messages sent to the old runtime are not replayed automatically. Persistent or recoverable state should use
net.sharedor an explicit channel snapshot protocol.Capabilities
Capability permissions follow #12.
Suggested target-qualified capabilities:
Runtime checks must apply to calls made from:
A package with only
clientcannot access UI, input, world data, or networking unless those permissions are also granted.This is capability control, not a perfect sandbox. A client cannot be protected from a malicious server that intentionally sends malicious code.
Dependencies
Client dependencies use the package dependency mechanism from #5. Dependencies must:
Cross-package script, resource, and API access is not implicit and requires a future explicit capability.
Errors and Diagnostics
Client errors should be isolated to the package and reported with:
Useful package states include:
Repeated or fatal errors may disable the package without affecting unrelated packages.
Configuration and Secrets
Client scripts may receive explicitly public client configuration, but server configuration and secrets must never be sent to the client.
Client scripts must not receive a client equivalent of the server secret API.
Non-Goals
Acceptance Criteria
Examples
Client Entrypoint
Package Resource
Client resources are resolved within the package and cannot escape its resource namespace.
Hot Reload
A package revision can keep durable state on the server while client-local runtime state is rebuilt:
Optional Package Failure
A failure in an optional client package disables that package only. Other packages and the client remain active.