From ba29407c19d51a5fcfc2f8a04259a5aee8570826 Mon Sep 17 00:00:00 2001 From: Dhvani Patel Date: Mon, 18 Aug 2025 09:44:09 -0600 Subject: [PATCH 1/6] programs diagram --- docs/pages/index.mdx | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/docs/pages/index.mdx b/docs/pages/index.mdx index e17c2a07..84f9fa36 100644 --- a/docs/pages/index.mdx +++ b/docs/pages/index.mdx @@ -9,9 +9,13 @@ Instead of separate mods or servers, you deploy your code into the single shard Command machines, turn chests into shops and doors into toll gates. Build currencies, reputation systens, governments and more. -On the following pages you'll learn about: +## Programs -- [Programs](/programs): Smart contracts that define the rules of your onchain entities -- [Apps](/apps): Custom user interfaces that integrate with the native client -- [Bots](/bots): How to automate tasks in DUST -- [Examples](/examples/swap-chest): Practical end-to-end use cases +``` +flowchart TD +W[W] -->|read T
call S
UI/explorer| Box +Box[ ] --> O(O) + + O -->|check hook
UI/explorer| Step1 + O -->|spawn the hook
UI/explorer| Step2 +``` From bc429f591312b37aaf7023027082f5b0029fc488 Mon Sep 17 00:00:00 2001 From: Dhvani Patel Date: Mon, 18 Aug 2025 09:44:48 -0600 Subject: [PATCH 2/6] fix --- docs/pages/index.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/pages/index.mdx b/docs/pages/index.mdx index 84f9fa36..470d608c 100644 --- a/docs/pages/index.mdx +++ b/docs/pages/index.mdx @@ -17,5 +17,5 @@ W[W] -->|read T
call S
UI/explorer| Box Box[ ] --> O(O) O -->|check hook
UI/explorer| Step1 - O -->|spawn the hook
UI/explorer| Step2 + O -->|spawn tile hook
UI/explorer| Step2 ``` From 525f66938f760f61c1003ee05eb98ff78dae334b Mon Sep 17 00:00:00 2001 From: Dhvani Patel Date: Mon, 18 Aug 2025 16:33:02 -0600 Subject: [PATCH 3/6] update --- docs/pages/apps/registration.md | 75 ------------- docs/pages/index.mdx | 13 +-- .../pages/{apps/index.md => programs/apps.md} | 0 docs/pages/{apps => programs}/dustkit.md | 0 docs/pages/programs/index.md | 103 ++---------------- docs/pages/programs/registration.md | 82 ++++++++++++-- docs/vocs.config.ts | 20 +--- 7 files changed, 89 insertions(+), 204 deletions(-) delete mode 100644 docs/pages/apps/registration.md rename docs/pages/{apps/index.md => programs/apps.md} (100%) rename docs/pages/{apps => programs}/dustkit.md (100%) diff --git a/docs/pages/apps/registration.md b/docs/pages/apps/registration.md deleted file mode 100644 index 03ca025f..00000000 --- a/docs/pages/apps/registration.md +++ /dev/null @@ -1,75 +0,0 @@ -# Registration - -## Preview an app in the client - -Before registering an app to make it available to everyone, you can preview your app in the production client by using the `debug-app` URL parameter. - -Example: `https://alpha.dustproject.org/?debug-app=https://your-dust-app.com/dust-app.json` - -## Register a global app - -To make an app available in everyone's client, you have to register it in the global app registry. - -1. Register a new MUD namespace. - - ```solidity - import { ResourceId, WorldResourceIdInstance, WorldResourceIdLib } from "@latticexyz/world/src/WorldResourceId.sol"; - import { ResourceIds } from "@latticexyz/store/src/codegen/tables/ResourceIds.sol"; - - IWorld world = IWorld(0x253eb85B3C953bFE3827CC14a151262482E7189C); - ResourceId appNamespaceId = WorldResourceIdLib.encodeNamespace(bytes14(bytes("your-dust-app"))); - if (!ResourceIds.getExists(appNamespaceId)) { - world.registerNamespace(appNamespaceId); - } - ``` - -2. Register by setting a resource tag that points to your ([app's manifest](https://esm.sh/pr/dustproject/dust/dustkit@d9cb17b/json-schemas/app-config.json)) - - ```solidity - import { metadataSystem } from - "@latticexyz/world-module-metadata/src/codegen/experimental/systems/MetadataSystemLib.sol"; - - metadataSystem.setResourceTag(appNamespaceId, "dust.appConfigUrl", bytes("https://your-dust-app.com/dust-app.json")); - ``` - -## Register a contextual app - -To show a contextual app when interacting with an entity that has [your program installed](../programs/registration.md), your program needs to implement the [`appConfigURI` function](https://github.com/dustproject/dust/blob/main/packages/dustkit/contracts/IAppConfigURI.sol). - -```solidity -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.24; - -contract CustomProgram { - function appConfigURI(EntityId viaEntity) external returns (string memory uri) { - return "https://your-dust-app.com/dust-app.json"; - } -} -``` - -## Register a spawn app - -Spawn apps are displayed on the spawn screen and should implement spawning functionality for custom spawn tiles. -Registering them is very similar to registering global apps, just using the `dust.spawnAppConfigUrl` resource tag instead. - -1. Register a new MUD namespace. - - ```solidity - import { ResourceId, WorldResourceIdInstance, WorldResourceIdLib } from "@latticexyz/world/src/WorldResourceId.sol"; - import { ResourceIds } from "@latticexyz/store/src/codegen/tables/ResourceIds.sol"; - - IWorld world = IWorld(0x253eb85B3C953bFE3827CC14a151262482E7189C); - ResourceId appNamespaceId = WorldResourceIdLib.encodeNamespace(bytes14(bytes("your-dust-app"))); - if (!ResourceIds.getExists(appNamespaceId)) { - world.registerNamespace(appNamespaceId); - } - ``` - -2. Register by setting a resource tag that points to your ([spawn app's manifest](https://esm.sh/pr/dustproject/dust/dustkit@d9cb17b/json-schemas/app-config.json)) - - ```solidity - import { metadataSystem } from - "@latticexyz/world-module-metadata/src/codegen/experimental/systems/MetadataSystemLib.sol"; - - metadataSystem.setResourceTag(appNamespaceId, "dust.spawnAppConfigUrl", bytes("https://your-dust-spawn-app.com/dust-app.json")); - ``` diff --git a/docs/pages/index.mdx b/docs/pages/index.mdx index 470d608c..89740303 100644 --- a/docs/pages/index.mdx +++ b/docs/pages/index.mdx @@ -9,13 +9,8 @@ Instead of separate mods or servers, you deploy your code into the single shard Command machines, turn chests into shops and doors into toll gates. Build currencies, reputation systens, governments and more. -## Programs +On the following pages you'll learn about: -``` -flowchart TD -W[W] -->|read T
call S
UI/explorer| Box -Box[ ] --> O(O) - - O -->|check hook
UI/explorer| Step1 - O -->|spawn tile hook
UI/explorer| Step2 -``` +- [Programs](/programs): How programs work +- [Bots](/bots): How to automate tasks in DUST +- [Examples](/examples/swap-chest): Practical end-to-end use cases diff --git a/docs/pages/apps/index.md b/docs/pages/programs/apps.md similarity index 100% rename from docs/pages/apps/index.md rename to docs/pages/programs/apps.md diff --git a/docs/pages/apps/dustkit.md b/docs/pages/programs/dustkit.md similarity index 100% rename from docs/pages/apps/dustkit.md rename to docs/pages/programs/dustkit.md diff --git a/docs/pages/programs/index.md b/docs/pages/programs/index.md index 65cfa018..fa6f7812 100644 --- a/docs/pages/programs/index.md +++ b/docs/pages/programs/index.md @@ -1,102 +1,19 @@ -Programs represent an abstraction layer that enables dynamic behavior and programmable logic for entities within DUST. Programs offer a flexible mechanism for extending entity functionality through custom logic that can respond to various in-world events. +## Programs -## Core Concepts - -Programs in this architecture are defined by several key abstractions: - -- **ProgramId**: A unique identifier that references a specific program implementation (the underlying value is a MUD `ResourceId`) -- **EntityId**: A unique identifier for entities within the world (ForceFields, Chests, etc) -- **Program**: Smart contracts (MUD systems) which can be attached to specific entities to define their behavior - -### Event-Driven Architecture - -Programs follow an event-driven architecture where system contracts invoke program hooks in response to specific actions. This pattern allows for: - -1. **Decoupling**: World systems and programs are loosely coupled, communicating through well-defined interfaces -2. **Extensibility**: New hooks can be added without modifying core systems -3. **Composability**: A program can be attached to different types of entities, and optionally implement the hooks related to those entity types - -### ProgramId and Resource Management - -The `ProgramId` type serves as the fundamental reference to program implementations. It is defined as: - -```solidity -type ProgramId is bytes32; ``` - -Programs are registered as systems within the MUD framework, and `ProgramId`s encode the corresponding `ResourceId` of the system. - -### Program Attachment and Validation - -The `ProgramSystem` contract manages the attachment and detachment of programs to entities: - -```solidity -function attachProgram(EntityId caller, EntityId target, ProgramId program, bytes calldata extraData) public { - // Validation logic - // ... - - EntityProgram._set(target, program); - - program.callOrRevert(abi.encodeCall(IAttachHook.onAttachProgram, (caller, target, extraData))); - - // Notification logic - // ... -} - -function detachProgram(EntityId caller, EntityId target, bytes calldata extraData) public { - // Validation logic - // ... - - ProgramId program = target.getProgram(); - - bytes memory onDetachProgram = abi.encodeCall(IDetachProgramHook.onDetachProgram, (caller, target, extraData)); - - (EnergyData memory machineData, ) = updateMachineEnergy(forceField); - if (machineData.energy > 0) { - program.callOrRevert(onDetachProgram); - } else { - program.call({ gas: SAFE_PROGRAM_GAS, hook: onDetachProgram }); - } - - EntityProgram._deleteRecord(target); - - // Notification logic - // ... -} +insert diagram ``` -A key aspect of program attachment is the validation process. Before a program can be attached to an entity, it must pass validation checks: - -1. The program must be registered as a private system -2. If the entity being programmed is within an active ForceField, the forcefield's program `validateProgram` hook must accept the attachment -3. The program itself must accept the attachment through the `onAttachProgram` hook +Programs can read from the world and influence it via smart objects. -This multi-layered validation ensures that only appropriate programs can be attached to entities, maintaining the integrity of the world. +The state of the world is stored in MUD tables. You can see the full list of tables on the mud.config. -### Gas Safety Mechanisms +To read the tables in a smart contract, you import the table library and then pass in the keys. To read the tables in an app/script, you can call a getter or use an indexer (eg. MUD stash). -To prevent malicious or poorly implemented programs from consuming excessive gas, calls with a fixed amount of gas are used. Safe calls are only used for hooks that correspond to actions that the entity might be incentivized to prevent (e.g. `onHit`). +Programs can be attached to smart objects in the world (eg chests, force fields), and the world will call your program for certain interactions. -The `SAFE_PROGRAM_GAS` constant (set to 1,000,000 gas units) limits the gas available to safe program calls, preventing the program from consuming all the gas available in the transaction. - -## Program Execution in Context - -### Machine Interactions - -Programs can respond to interactions with machines, such as when a player fuels a machine: - -```solidity -function fuelMachine(EntityId callerEntityId, EntityId machineEntityId, uint16 fuelAmount) public { - // Validation and fuel logic - // ... - - // Call program hook - ProgramId program = baseEntityId.getProgram(); - program.callOrRevert(abi.encodeCall(IFuelHook.onFuel, (callerEntityId, baseEntityId, fuelAmount, ""))); - - // Notification logic - // ... -} -``` +Smart contract programs deployed to the DUST world will automatically show up in the explorer. You can also register a custom UI for your program in 3 different places: -This allows machines to execute custom logic when they receive fuel, potentially triggering complex behaviors or state changes. +- sidebar: a list of global apps +- spawn screen: a list of apps shown on the spawn screen +- when interacting with smart items: shown when a user interacts with a smart object with your program attached diff --git a/docs/pages/programs/registration.md b/docs/pages/programs/registration.md index e7cecd36..03ca025f 100644 --- a/docs/pages/programs/registration.md +++ b/docs/pages/programs/registration.md @@ -1,17 +1,75 @@ -## Attaching Programs to Entities +# Registration -To attach a program to an entity: +## Preview an app in the client -1. **Find Program ID**: Get the resource ID of the registered system. The easiest way to find it is to look in the MUD codegenerated system library. For example, [this is where you would find the resource ID of the default program](https://github.com/dustproject/dust/blob/6ad697a45c99ee3418196968b01cc38c73626aec/packages/programs/src/codegen/systems/DefaultProgramSystemLib.sol#L21). -2. **Be in proximity**: Move close to the smart entity with a player that has access to upgrade the smart entity's program. By default the player who placed the smart entity has access to update its program. -3. **Find the smart entity's ID**: The easiest way to find it is to right click the smart entity in the client to open its UI, then click on the small square next to the window title. From there you can select the truncated hex after "Entity" (i.e. `Entity: 0x0300...0000`) and press CTRL+C to copy it. Despite the truncation, it will copy the entire ID. - ![EntityId](/smart-entity-id.png) -4. **Call the `world.updateProgram` function**: You need to call it from the account of the player standing close to the smart entity. The easiest way to do this is to connect the player's account to the [MUD World Explorer and call the function from there](https://explorer.mud.dev/redstone/worlds/0x253eb85B3C953bFE3827CC14a151262482E7189C/interact?expanded=root%2C0x7379000000000000000000000000000050726f6772616d53797374656d000000&filter=updatePro#0x7379000000000000000000000000000050726f6772616d53797374656d000000-0x843bb3c7cebac75f0b0ba241960c28d7148f695cb075442ee297284be1b22360). +Before registering an app to make it available to everyone, you can preview your app in the production client by using the `debug-app` URL parameter. + +Example: `https://alpha.dustproject.org/?debug-app=https://your-dust-app.com/dust-app.json` + +## Register a global app + +To make an app available in everyone's client, you have to register it in the global app registry. + +1. Register a new MUD namespace. + + ```solidity + import { ResourceId, WorldResourceIdInstance, WorldResourceIdLib } from "@latticexyz/world/src/WorldResourceId.sol"; + import { ResourceIds } from "@latticexyz/store/src/codegen/tables/ResourceIds.sol"; + + IWorld world = IWorld(0x253eb85B3C953bFE3827CC14a151262482E7189C); + ResourceId appNamespaceId = WorldResourceIdLib.encodeNamespace(bytes14(bytes("your-dust-app"))); + if (!ResourceIds.getExists(appNamespaceId)) { + world.registerNamespace(appNamespaceId); + } + ``` + +2. Register by setting a resource tag that points to your ([app's manifest](https://esm.sh/pr/dustproject/dust/dustkit@d9cb17b/json-schemas/app-config.json)) + + ```solidity + import { metadataSystem } from + "@latticexyz/world-module-metadata/src/codegen/experimental/systems/MetadataSystemLib.sol"; + + metadataSystem.setResourceTag(appNamespaceId, "dust.appConfigUrl", bytes("https://your-dust-app.com/dust-app.json")); + ``` + +## Register a contextual app + +To show a contextual app when interacting with an entity that has [your program installed](../programs/registration.md), your program needs to implement the [`appConfigURI` function](https://github.com/dustproject/dust/blob/main/packages/dustkit/contracts/IAppConfigURI.sol). ```solidity -world.updateProgram( - entityId, // ID of the smart entity, i.e. chest - programId, // MUD system ID of the program - "" // optional extra data -); +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.24; + +contract CustomProgram { + function appConfigURI(EntityId viaEntity) external returns (string memory uri) { + return "https://your-dust-app.com/dust-app.json"; + } +} ``` + +## Register a spawn app + +Spawn apps are displayed on the spawn screen and should implement spawning functionality for custom spawn tiles. +Registering them is very similar to registering global apps, just using the `dust.spawnAppConfigUrl` resource tag instead. + +1. Register a new MUD namespace. + + ```solidity + import { ResourceId, WorldResourceIdInstance, WorldResourceIdLib } from "@latticexyz/world/src/WorldResourceId.sol"; + import { ResourceIds } from "@latticexyz/store/src/codegen/tables/ResourceIds.sol"; + + IWorld world = IWorld(0x253eb85B3C953bFE3827CC14a151262482E7189C); + ResourceId appNamespaceId = WorldResourceIdLib.encodeNamespace(bytes14(bytes("your-dust-app"))); + if (!ResourceIds.getExists(appNamespaceId)) { + world.registerNamespace(appNamespaceId); + } + ``` + +2. Register by setting a resource tag that points to your ([spawn app's manifest](https://esm.sh/pr/dustproject/dust/dustkit@d9cb17b/json-schemas/app-config.json)) + + ```solidity + import { metadataSystem } from + "@latticexyz/world-module-metadata/src/codegen/experimental/systems/MetadataSystemLib.sol"; + + metadataSystem.setResourceTag(appNamespaceId, "dust.spawnAppConfigUrl", bytes("https://your-dust-spawn-app.com/dust-app.json")); + ``` diff --git a/docs/vocs.config.ts b/docs/vocs.config.ts index f33732b7..b9e93a15 100644 --- a/docs/vocs.config.ts +++ b/docs/vocs.config.ts @@ -9,25 +9,15 @@ export default defineConfig({ sidebar: [ { text: "Overview", - items: [ - { text: "Introduction", link: "/" }, - { text: "Data Model", link: "/data-model" }, - ], + items: [{ text: "Introduction", link: "/" }], }, { - text: "Programs (contracts)", + text: "Programs", items: [ { text: "Introduction", link: "/programs" }, - { text: "Registration", link: "/programs/registration" }, - { text: "Reference", link: "/programs/reference" }, - ], - }, - { - text: "Apps (client)", - items: [ - { text: "Introduction", link: "/apps" }, - { text: "Registration", link: "/apps/registration" }, - { text: "DustKit", link: "/apps/dustkit" }, + { text: "Reading The World", link: "/programs/reading-the-world" }, + { text: "Smart Objects", link: "/programs/smart-objects" }, + { text: "Custom UIs", link: "/programs/custom-uis" }, ], }, { From 994a11dcc5a6a0bd0d110e4ad08e85b0316ac5a3 Mon Sep 17 00:00:00 2001 From: Dhvani Patel Date: Tue, 19 Aug 2025 10:12:37 -0600 Subject: [PATCH 4/6] update --- docs/pages/programs/{ => custom-uis}/dustkit.md | 0 .../programs/{apps.md => custom-uis/index.md} | 2 +- .../programs/{ => custom-uis}/registration.md | 6 ++++++ docs/pages/programs/index.md | 4 +--- .../reading-the-world.md} | 6 +++--- .../programs/{reference.md => smart-objects.md} | 0 docs/public/program-diagram.png | Bin 0 -> 6375 bytes docs/vocs.config.ts | 10 +++++++++- 8 files changed, 20 insertions(+), 8 deletions(-) rename docs/pages/programs/{ => custom-uis}/dustkit.md (100%) rename docs/pages/programs/{apps.md => custom-uis/index.md} (99%) rename docs/pages/programs/{ => custom-uis}/registration.md (89%) rename docs/pages/{data-model.md => programs/reading-the-world.md} (98%) rename docs/pages/programs/{reference.md => smart-objects.md} (100%) create mode 100644 docs/public/program-diagram.png diff --git a/docs/pages/programs/dustkit.md b/docs/pages/programs/custom-uis/dustkit.md similarity index 100% rename from docs/pages/programs/dustkit.md rename to docs/pages/programs/custom-uis/dustkit.md diff --git a/docs/pages/programs/apps.md b/docs/pages/programs/custom-uis/index.md similarity index 99% rename from docs/pages/programs/apps.md rename to docs/pages/programs/custom-uis/index.md index 002aefd0..a953f86b 100644 --- a/docs/pages/programs/apps.md +++ b/docs/pages/programs/custom-uis/index.md @@ -1,4 +1,4 @@ -# Introduction +# Custom UIs The DUST client supports embedded apps - web apps that integrate directly into the game client UI and interact with in-game objects and physics. Apps let developers build on top of the world and extend the game client with custom functionality like shops and marketplaces. diff --git a/docs/pages/programs/registration.md b/docs/pages/programs/custom-uis/registration.md similarity index 89% rename from docs/pages/programs/registration.md rename to docs/pages/programs/custom-uis/registration.md index 03ca025f..9b4beba1 100644 --- a/docs/pages/programs/registration.md +++ b/docs/pages/programs/custom-uis/registration.md @@ -1,5 +1,11 @@ # Registration +Smart contract programs deployed to the DUST world will automatically show up in the explorer. You can also register a custom UI for your program in 3 different places: + +- sidebar: a list of global apps +- spawn screen: a list of apps shown on the spawn screen +- when interacting with smart items: shown when a user interacts with a smart object with your program attached + ## Preview an app in the client Before registering an app to make it available to everyone, you can preview your app in the production client by using the `debug-app` URL parameter. diff --git a/docs/pages/programs/index.md b/docs/pages/programs/index.md index fa6f7812..f637d654 100644 --- a/docs/pages/programs/index.md +++ b/docs/pages/programs/index.md @@ -1,8 +1,6 @@ ## Programs -``` -insert diagram -``` +![program diagram](/program-diagram.png) Programs can read from the world and influence it via smart objects. diff --git a/docs/pages/data-model.md b/docs/pages/programs/reading-the-world.md similarity index 98% rename from docs/pages/data-model.md rename to docs/pages/programs/reading-the-world.md index 79cbd337..916b1a86 100644 --- a/docs/pages/data-model.md +++ b/docs/pages/programs/reading-the-world.md @@ -1,4 +1,4 @@ -# Data Model +# Reading The World DUST is built using [MUD](https://mud.dev/), and so the [mud.config.ts](https://github.com/dustproject/dust/blob/main/packages/world/mud.config.ts) defines all the different entities and components. Here we look at 2 specific kinds of data and how to read them. @@ -95,7 +95,7 @@ SELECT "entityId", "x", "y", "z" FROM "EntityPosition" WHERE "entityId" = '0x01c This shows the player is at `[1380, 79, -2434]`. -### Reading In A Program +### Reading In A Smart Contract ```solidity import { EntityId, EntityTypeLib } from "@dust/world/src/types/EntityId.sol"; @@ -185,7 +185,7 @@ SELECT "owner", "slot", "entityId", "objectType", "amount" FROM "InventorySlot" This shows all the items the player has. eg, the player has 4 WheatSeeds (id 134) in slot 2. -### Reading In A Program +### Reading In A Smart Contract ```solidity import { InventorySlot, InventorySlotData } from "@dust/world/src/codegen/tables/InventorySlot.sol"; diff --git a/docs/pages/programs/reference.md b/docs/pages/programs/smart-objects.md similarity index 100% rename from docs/pages/programs/reference.md rename to docs/pages/programs/smart-objects.md diff --git a/docs/public/program-diagram.png b/docs/public/program-diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..d6b699a45f2117e00b72b52ea407128913633afb GIT binary patch literal 6375 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 Date: Tue, 19 Aug 2025 10:46:56 -0600 Subject: [PATCH 5/6] update --- .../pages/programs/custom-uis/registration.md | 2 +- docs/pages/programs/smart-objects/index.md | 96 +++++++++++++++++++ .../reference.md} | 0 docs/vocs.config.ts | 11 ++- 4 files changed, 107 insertions(+), 2 deletions(-) create mode 100644 docs/pages/programs/smart-objects/index.md rename docs/pages/programs/{smart-objects.md => smart-objects/reference.md} (100%) diff --git a/docs/pages/programs/custom-uis/registration.md b/docs/pages/programs/custom-uis/registration.md index 9b4beba1..ac1edf69 100644 --- a/docs/pages/programs/custom-uis/registration.md +++ b/docs/pages/programs/custom-uis/registration.md @@ -40,7 +40,7 @@ To make an app available in everyone's client, you have to register it in the gl ## Register a contextual app -To show a contextual app when interacting with an entity that has [your program installed](../programs/registration.md), your program needs to implement the [`appConfigURI` function](https://github.com/dustproject/dust/blob/main/packages/dustkit/contracts/IAppConfigURI.sol). +To show a contextual app when interacting with an entity that has your program installed, your program needs to implement the [`appConfigURI` function](https://github.com/dustproject/dust/blob/main/packages/dustkit/contracts/IAppConfigURI.sol). ```solidity // SPDX-License-Identifier: MIT diff --git a/docs/pages/programs/smart-objects/index.md b/docs/pages/programs/smart-objects/index.md new file mode 100644 index 00000000..4dbd6e5e --- /dev/null +++ b/docs/pages/programs/smart-objects/index.md @@ -0,0 +1,96 @@ +# Smart Objects + +Programs can be attached to smart objects in the world (eg chests, force fields), and the world will call your program for certain interactions. + +## Core Concepts + +Programs in this architecture are defined by several key abstractions: + +- **ProgramId**: A unique identifier that references a specific program implementation (the underlying value is a MUD `ResourceId`) +- **EntityId**: A unique identifier for entities within the world (ForceFields, Chests, etc) +- **Program**: Smart contracts (MUD systems) which can be attached to specific entities to define their behavior + +### ProgramId and Resource Management + +The `ProgramId` type serves as the fundamental reference to program implementations. It is defined as: + +```solidity +type ProgramId is bytes32; +``` + +Programs are registered as systems within the MUD framework, and `ProgramId`s encode the corresponding `ResourceId` of the system. + +### Program Attachment and Validation + +The `ProgramSystem` contract manages the attachment and detachment of programs to entities: + +```solidity +function attachProgram(EntityId caller, EntityId target, ProgramId program, bytes calldata extraData) public { + // Validation logic + // ... + + EntityProgram._set(target, program); + + program.callOrRevert(abi.encodeCall(IAttachHook.onAttachProgram, (caller, target, extraData))); + + // Notification logic + // ... +} + +function detachProgram(EntityId caller, EntityId target, bytes calldata extraData) public { + // Validation logic + // ... + + ProgramId program = target.getProgram(); + + bytes memory onDetachProgram = abi.encodeCall(IDetachProgramHook.onDetachProgram, (caller, target, extraData)); + + (EnergyData memory machineData, ) = updateMachineEnergy(forceField); + if (machineData.energy > 0) { + program.callOrRevert(onDetachProgram); + } else { + program.call({ gas: SAFE_PROGRAM_GAS, hook: onDetachProgram }); + } + + EntityProgram._deleteRecord(target); + + // Notification logic + // ... +} +``` + +A key aspect of program attachment is the validation process. Before a program can be attached to an entity, it must pass validation checks: + +1. The program must be registered as a private system +2. If the entity being programmed is within an active ForceField, the forcefield's program `validateProgram` hook must accept the attachment +3. The program itself must accept the attachment through the `onAttachProgram` hook + +This multi-layered validation ensures that only appropriate programs can be attached to entities, maintaining the integrity of the world. + +### Gas Safety Mechanisms + +To prevent malicious or poorly implemented programs from consuming excessive gas, calls with a fixed amount of gas are used. Safe calls are only used for hooks that correspond to actions that the entity might be incentivized to prevent (e.g. `onHit`). + +The `SAFE_PROGRAM_GAS` constant (set to 1,000,000 gas units) limits the gas available to safe program calls, preventing the program from consuming all the gas available in the transaction. + +## Program Execution in Context + +### Machine Interactions + +Programs can respond to interactions with machines, such as when a player fuels a machine: + +```solidity +function fuelMachine(EntityId callerEntityId, EntityId machineEntityId, uint16 fuelAmount) public { + // Validation and fuel logic + // ... + + // Call program hook + ProgramId program = baseEntityId.getProgram(); + program.callOrRevert(abi.encodeCall(IFuelHook.onFuel, (callerEntityId, baseEntityId, fuelAmount, ""))); + + // Notification logic + // ... +} +``` + +This allows machines to execute custom logic when they receive fuel, potentially triggering complex behaviors or state changes. diff --git a/docs/pages/programs/smart-objects.md b/docs/pages/programs/smart-objects/reference.md similarity index 100% rename from docs/pages/programs/smart-objects.md rename to docs/pages/programs/smart-objects/reference.md diff --git a/docs/vocs.config.ts b/docs/vocs.config.ts index 093d0a84..135b848a 100644 --- a/docs/vocs.config.ts +++ b/docs/vocs.config.ts @@ -16,12 +16,21 @@ export default defineConfig({ items: [ { text: "Introduction", link: "/programs" }, { text: "Reading The World", link: "/programs/reading-the-world" }, - { text: "Smart Objects", link: "/programs/smart-objects" }, + { + text: "Smart Objects", + link: "/programs/smart-objects", + collapsed: false, + items: [ + { text: "Overview", link: "/programs/smart-objects" }, + { text: "Reference", link: "/programs/smart-objects/reference" }, + ], + }, { text: "Custom UIs", link: "/programs/custom-uis", collapsed: false, items: [ + { text: "Overview", link: "/programs/custom-uis" }, { text: "Dustkit", link: "/programs/custom-uis/dustkit" }, { text: "Registration", link: "/programs/custom-uis/registration" }, ], From 629a203aac43e4327beb618dd5568cf7940f0bf6 Mon Sep 17 00:00:00 2001 From: Dhvani Patel Date: Tue, 19 Aug 2025 12:51:14 -0600 Subject: [PATCH 6/6] update --- .../programs/smart-objects/registration.md | 17 +++++++++++++++++ docs/public/program-diagram.png | Bin 6375 -> 8767 bytes docs/vocs.config.ts | 4 ++++ 3 files changed, 21 insertions(+) create mode 100644 docs/pages/programs/smart-objects/registration.md diff --git a/docs/pages/programs/smart-objects/registration.md b/docs/pages/programs/smart-objects/registration.md new file mode 100644 index 00000000..e7cecd36 --- /dev/null +++ b/docs/pages/programs/smart-objects/registration.md @@ -0,0 +1,17 @@ +## Attaching Programs to Entities + +To attach a program to an entity: + +1. **Find Program ID**: Get the resource ID of the registered system. The easiest way to find it is to look in the MUD codegenerated system library. For example, [this is where you would find the resource ID of the default program](https://github.com/dustproject/dust/blob/6ad697a45c99ee3418196968b01cc38c73626aec/packages/programs/src/codegen/systems/DefaultProgramSystemLib.sol#L21). +2. **Be in proximity**: Move close to the smart entity with a player that has access to upgrade the smart entity's program. By default the player who placed the smart entity has access to update its program. +3. **Find the smart entity's ID**: The easiest way to find it is to right click the smart entity in the client to open its UI, then click on the small square next to the window title. From there you can select the truncated hex after "Entity" (i.e. `Entity: 0x0300...0000`) and press CTRL+C to copy it. Despite the truncation, it will copy the entire ID. + ![EntityId](/smart-entity-id.png) +4. **Call the `world.updateProgram` function**: You need to call it from the account of the player standing close to the smart entity. The easiest way to do this is to connect the player's account to the [MUD World Explorer and call the function from there](https://explorer.mud.dev/redstone/worlds/0x253eb85B3C953bFE3827CC14a151262482E7189C/interact?expanded=root%2C0x7379000000000000000000000000000050726f6772616d53797374656d000000&filter=updatePro#0x7379000000000000000000000000000050726f6772616d53797374656d000000-0x843bb3c7cebac75f0b0ba241960c28d7148f695cb075442ee297284be1b22360). + +```solidity +world.updateProgram( + entityId, // ID of the smart entity, i.e. chest + programId, // MUD system ID of the program + "" // optional extra data +); +``` diff --git a/docs/public/program-diagram.png b/docs/public/program-diagram.png index d6b699a45f2117e00b72b52ea407128913633afb..ef5f844bd376da750b2588f4ea770494a09dfb60 100644 GIT binary patch literal 8767 zcmeHtc{H2f*Kd@f-xftn)l{_AZJC(**( zSm>{le*pjhA(K0X4*&oj6aa8QQ;?raQOttJb6);coodjAvBo1~yz7^1`QxGP|hfqovSG7Kh(}cywK*7`qdo=lS zeaq!+_#D2Z%okkZ{Kc4w?(o~94AsJ6nBQ=uYgI^P}?q%SMV@95>EV;@ON0WG_7JcQSk8-=U+A=2X1X~oF^a`PsEVL74C=(~({ z6@5QFa^E1D?QkvMRkA<#5%z39b=A7Fisi;Kg5z|j_VI0fHn*t+#B1FG+WZz!{I z7itRd8+ddcRq9YToA{UN`Nrf|VRH^ne6D`LnAB5=M-#vQdT-;TQ}wejJBjKZlRKWA zKiBF6v$r!_)UE9oL}w|YD6oA_N0|yjl9aOG?p#-P7}&P$wmYA3Kh;?;FYSvBbsC^a z|7Vv;H+bs1v09I>YH+afgvhDgoLTJ8QE`??KW_QLjL#bRkGgJ%;i;E_1CE4Obf;s=FYdnm; zd|~$6wX%Fa;n%TA=4Wo1J&)$x6D(9uDmbzpWdJHXpZvGSog@hQB=nIr_#_abD3XV5%)zMhr3(ZWRh?^RpcEv<>r|PO&y=GS`sa8ka6VRc{is zItf2K%u|(DndTwZA@)q-;R`z}6MEb3nimx}%hYCG38g$0r`8qLLFs7bwG|Vohe#r? zieMCzq4y&KXX;VMQ-g3(a5L9izH4B1B@8%;qq2pf+ zUoGBI8k#&&EzPt0Yv$V=HhJqep9>T*Z1^!w#eg{(f+a6vFX%BF)q;fVU)@!`)#y7? zEz-W@|M~eY6SE&ZxpFbi(=_++;ahN%vA=o!D(YW_fB-FCDm;>oEQaE;HbztP?j0~1 zU$vj}mggk=@s8r;Sdj2VXju1|qX0k3qXTMRRe@QlM{zTivvsjqvyrHuKSkeLAE|3y zGM5uA5WOyn8X7vSwwh{@9Gw(xZ+!6M`0&DRzuw<~8Y%l0(^EH9viXz-gS9KF1m6qh z2{xN0x2LSED~1-jt)xav@ksB2CQ{X;2$mnaeLLS3)dFf_?4`@f%b{AaHOr;@IqBhY zf;r7PKKlWJEu9vQnc~i%S-Oh7kVGP;3w5?(N=gND?&VYcD523VUB|>&_i#ZJuJ@8j zQJ2evpmvbC_G9fGG}N8bD&19N?5iJzG;gmpx7pIw2B41oOWMy~+OG-7Z=V{(bjw9& zx#hM|^I!Ujk`Hz--OV?5ZU`b0DOKFYdlZ_yC|_N>B ztBxn*K_f$9ucSY8N6&(nx<3a=7Z9k{W&D~dw^Z9&g~&g(x>{R}9#ZZd)y}Ux=pfU+ z-jRb8hWwiD2>L7`54iL_a?(IG4@Y{|+4||c2-t*jR(Wr2?jF{nr)vt*cNj8dIL$_k z8A=_u)74mT2jYRbnO;T9lmr6=oI%Ht_h32W2v6_4Y`(pZnl zv<)N~Jx(6Hwg3)>Thrwzj)4_T4f`__9CFGdq*7p2&Wr>Sow(0n@ApX0#7DFdIh1J= zB9Swl7qhc8CqiXT!C*@XkryO!2?-+dc{<9XTCmrrRy8}i)YoZR*}A*U%!wpDu<@CR z{aCO7h^y**_BqH^_g6_+@pVNXHUuNrO(|`JfL9s@)#*u zBzF6(jJ0#?qbPT|-GW!3WXQU!*etJdK0QA0#Z5jpKdmooZ7`azvtB(-5EViD#5_St z3rVwU>;lhSLP%H3W3a8;=_}QlQ*3vZ;IsqKN#)u`v)1tI5G_m2VuH6?^~l$)uBnHU zof==(GT&S40=w8xJNR>I-p3}(dCb)Hdx$EE2Gqh!otkgFbv-J-XE`HuWwTS@-qm~_ zsad8VGD~tsiXY<0Ux!ykg+RPohciRdYtPX*H5^soZU&K%1NDA7fH6E}guCsk z%W~0q{+$94&2^!~i?0H)q7p%(kSW{tMXhDrDzz?fLG<>m(I2)pdz1UMgGWTOEzyo! z$-LJO+WQptiEQBe@(we5d2&=H1D@icy8q$pSXpl z(YpIpA2Ubag2&d+_g0wa{jQ%gsM2xg7dB2VWb`(~PT*}i74@}Q&&dJtcKMN?BYDWS zIr!?=TDeaORh7xh_knj~RrwuWJmhnLZcw!fWW6?uf3|?v8 zQ^CtSX&A=Gd)%$X2MJB4w+aJ4KsHBh@O}6d#!Z91%Xh+kV!dN=#ARA=Veh5*>7=sL z)zUZ-YJhRF-+*-UlMClNwn1s%&&1&0mQcR z$ss{xk>wgc)3$V_kMpP)Nb;Lj7wAy49Vcdx>E? zhl^7jQGNG-=pQlb$<7Tx7z-z~%@zZ#7U%D?scF_v^J8{=cE>S$qZ9}g4yF#k=kn=_Y~!9EK=_ms{BmO|T6!@Vya6qeyEb{X8c2(8}4 zRn5R|{2zAG%Fzls246~#+8oCTSB}Ft4w`wmg*q`=X#X0!mLms68_|(1WKXm98Wk(h zI|hdi!*G^uFuIHzvhN+Qa@_!S5oe>sK!5L|h{1M;2(cQ*S;2ouUIvcgS$_}EHgj&b zxRI*STf~o>g=MeDBW7MorjBz~ocDJoTL1VVv~bKWc^zC-)JNy?gMY620mpc??yx%I zy!Sg3`y7bK+#QvymfWFhjqi@+?}f5YyboZ8-HI}K#mK6`@yRUA3v5~h;?id};q)l# zHt*h_sl6D5%gGl7FBwp~7(oHRF`UcKBoGdeVv&@d(w^Du0Wkv~VnHjBou z%eiqr`D57;oNfIr6{La0o%I-Z1H}fnCJcubnD2ji>+%6LimwLiHVUgb*A!7H=tZzT zkQ!WfNKH-ZgqXr{S&JRU+%1EzrwomLa6oClUY$yOs`C7kJr`FO2eEp&Plq~c}(uOP@gSb3&ZLUI!+*GmKuiS)RgDO6}>Bs zQa5duCpwCrzBMB$zbf_Mmi+3vgO!e*&gZLv$2+(#)LLY6XS2Jj*v!ceO)H&ldJ`>X z#5)PFvi6X9)zd0~11s6q|Io{bt0}kQ3R8@6BKzXnBR%&|>RVCqW40ILy@zp0=VFAV zE{ICFk5h~aULUjw2!G?rBh{UKBVnwc6KZ+&{UL4e@E?gBf{1Di`OLSTIC$Lz^y;`#IxKpQp=(=2%_cDiZsnS$6V3t!m3Ts41WOqky5_)4GKl0`JBQ z4}Ig4>GL#BGjiMsaY8=41HPMRe%`(4#f27A7gMlI;&o}g5`wS$a2jJ0bP{5 zAvT_Ct^F0%7WS!|YGWzB^L1rOj1W-^T>SFm(+Py6ztppD zFe1@}v_E$nJ7;&mOaDKE8`?#BCt-Y}o&g0Xk58vaYYiMhM9JyQ!d`v%pJ)vG1u1zR zCz`Aa-dHdqxGTN&xEQ@z?(8ploTp{&o>GB2+|)&=P&fHW|EGb!Ef1HV>l7nwpqFL^)RqO zFMLe{pthKtqN4PbwmI%>u*c#Chy4GTq;)(Hz43rI)-jCX9jT#C_G{PNOfzvYO8#or z)3J3(8x?E)rv^TxtzE)+k&_-r69s{UB@byS2#^$}U5wmkH0{@Q6a?;nh>bgb;VeBV_L68s^7F6gcsK#{!yrpd~5nL{psb!2n_kOV; zNF5N?3>+Jf3@(?+2ONdo%nhM?t^l`-o2!kUvq!0%{bwjbjz6 zB2Be(4^SOCg4z+kpZEv$2+3xC6&f&U?*R7^BYG--=YrzS@X7e$D})5b&yQ5_9Omlf zDm(Fj8qov7h`_NI(iQO7xN_G&mG|6=JGTNb>dyx87L-m5ZA^Pd61ar_WdWUGW4g0e zj7Rnjvbkcm|F~vy)w{X&YcSM!WW!tcYWB5&ociS$k&lfT%wU3MOB%p$Xu~@)R72mP zrmvBueo5c9X0o_`@=O0>bw~?*3e9RB;jfu%Bb*2sG%w2%tToa5=jqNDc)l{j)4-`_ z@&@miLTaP8e?%2r20HT%9Q;~V!{r}6T5%!7(_@bYe*CdZqh%UA<}+pm`XSLFp{kO0 z`^z>dzv1NUyKfC}K3(d@%Wg$_zkG=jqZ%(yACS*?uAA)WVfV7HxINxT$Q_+;q#!aY zwHn|K^vF1}=!}H(iMhaS?pZ8(?<@{&kc*Z^!NbGqD;MflW}`bL@AF`ZX2fj>DwkQn z44K+I$e4AB##HVJg%Ur)qTBi%ymXzPj7ivlfXMo(7I#88HN<{2jU zep!Hi)EMa3L3IgJM~dNm-v!{15!PG+df_Z>oe}0fN|L5+oYKzFkB&I17fk`zzDnG;rQ|!sJmzVA^1h3=FvfW+WMo{ zYeAQOd>6%z5Hj0+^!zzL&FRRQ@4nUWN1r(3*&AD7-p?Bom(T8%u5IPfwPfdmIW7>w zfaWESpR`8T@!MlLh!Nf>^}01b4+C`#FQpZ;oY=W#+}vymI0n*3$qQ7L3k=IkqdI-u zj|#j7OdE}=qNh;UwqDh6e#&5N^Y}!XF+6%HPpfrw0?)34wm4QWPQ3o^OKzSzS$_b! ze*o7A-XamxIbeihE@GWmwVyZ5@~d>TX-rk0~lk+xH#-WT|QgCzI| z+3yH2uvFrL>PpuH%nVhgc%d16Mk#AHd4d}Q$T*Hze*$F_lHHj!7Ji-W6vz@GF4YF1 zk(A4{VgWB0JiYQ79_cFJ4`~lNbRgi0lm!dRnPso9^#pU}gTagQ6z_DkU!B{U_n*5m z)A}bqA2Vr=v7&e@A1B8l*t)1sz~BTlwoQbZHqJk$cfyW3_aq7O~SZ=k)EUH|2|J5v^s@l>sqpy@koE?}aag$dwW9bsV6-H#5TMm?bretbtyuzW?$ zWDWx>*by^&Od*5(l1bW8TaIdBWM@!Xw>c^NHuf?Pp|lOc$@-@mY|n+vE)ICu>SS8N z{a>4QH`^<#>`i15>v9RZDi^{B&2Op+$9fZAT_^dLDEC$)M?|H@JTdSWlZ`CjP-g~* zwc_(&=6p^=2G9)%v{Tayj~>)i`Hwup*?m>+>FD{DjQ;3&zHrScs0j@oE*8agYYl6I zyciISzw)Gu9?H6^J3W-M+Yp}X88vE*cXz;f!zW5T4Z+eA8|&2VG^>(4pMz<&Fa?ERk#D5$%T z4?NKTpKRJCM(O$~Grd|`JkTh|ADgUTYE$53F!bGiFN~}xSq{5TD{kn*%bY$h|2oC zIM&t{(Puh^b{JrPRQAs7H^{^pfMfleuq~3$^-erz5se0)8)WIw%!(2}(c ztT>O>%x4x<$~CxRRQHhC2>dgkMlAWF&r0}UH*jo^#$IP3CX*Rhq~rz047--35mC>4 z94#+zHgz>S@8f=JnTYQ06{#%P;5C6Z0s#DNhP|#ho-dnvb${l0nEL6n{!I>x3z|`s zs+PVGUOe1O z?8+_CwGV?{dPi{5J2c6)A|I~$Df4VUQ891lX_R1 zM6-2wi9>i+3Rme+x81T488xt=JqC9?$6hE_#kFD%n{EPSyZx-xq-ajY1(k*v zgE0ny2Qydti7eG+kGh@;_}0;b(@9x~(W<#+Cjrf4ZoB?$_1JeajJtH!CzsxPC)OH+ z>cQX_Z>`5#?f}30NXsYGx2zTOfIL@`BcN=dzI+507@T*3&}|E7t99Xga#`YEmR8z* zw_=8`by~w(08&$ihlxI)^&0v!Nxwde0~~lDJfZw#G;5z{>T&zG#dI_*v(wX{ca-k1(ML8G#&NL-*mX+3DIJt8Gpb~;a{mL+<`zHHH2(x;$ci#*CHKg`N$^wFC*}M zf6arD$LAVehjX7D3mNDfJo@R-^bwfl!4Sg)ap9(8rw=w8-!(gSXUtuqE#SRhW)9s- zV&bmf@M31V{}fzP$rBlVx|OlQ=ZR8yROw{e0l?)!b4|du@06{abzuBgLUN%TZZ3U99ZV`p5WTX8lAu02Z*WTkZ_f#vF z>oz4X>HtaO`q;Ie6K1V=cbSE95s&Q+L{RtQP28Lnvc*}tMn|$%exUGJ==uQ9emM)Y zaj;WTDej$8DWya2C#h*t&19m}t6vKfu8sbC^&WKN^B}A}wuP2BBA!kkHS1Sb3AZAGoEvVCNaxN{cC%7h~T{IesRZJv6{u<;a zug{lUDp3rn9bAOb6hg$TA5Hd=mnu0`_6-8iJ!@{P*o>30UanlDL#wuiHa1Zk|CQcp zzmdsGpvl={A4KBsEbQbX?BO|iDbi>wFse0l80HyRYtYn`xl&(8n}({^nb-~gd>MF) zB?y6^SFBHl*o?k>SAvSwcw-S2dBPv^{Xp_XYB8r;wZY}d5|ajJ=R7rYkov zosDhErw8YygeJ3^#}dW7Ep|P*a^3j^a-GVtnaWLTJRxRq2b_B1&5(~iqPyoUH^IPH zQvd^B7dQT>wmT0o{h~@>CP$u6c>Xwi0sr!VuyeOCT6+%GI-NX)m zoZEn0Wuz?eiDsS>`I~~!v%kcg|cpJ&mmx{+{rd}J#rPdaU3>FI8LR1rF!VOU69 zo3~vxcuEWvq=e=tjK6vsBMyv8&PMTVNnSQCdy8^ov*Y9DN>y;aiReH)I7~4*jI9QH z!h2Lc>*9(!DpZG*w@V#WUr*(%GRV;JvIhgh3hNub;TPL4H09l2;t#(pB$hf$ZL+|7 zpR`SSw`~S{3%3{two!e|`+N(!Q1yyAb7)z*)tMsgJb|3yGKKw7A)U*fz3^2qgk)R*rMv3z!ye$WHHe&S878^c0(?sRhEQl=ClULy^dlegJY z9P4>=75{uNYCK#zC7+b~cm~B^d(0nL3AyI(*GLQgx!$(gjOmwX0j-#KbK3vv;T$B| zFwYg@lJ!Av=GB#}N*C_y;NQ*&zc)=OKW#HrQZSJCerfIt?iIUm-93o1biT8;jSp@@ z7kcJ7ztS4f(mOZz%1S>ZW)v>Km$y}BJ}h8FNr3GRe+@{v;L9wVkF8#m-s|}X6CH25 zgxZGE5t$Og5I;aO*7jRby|k%xn05f0{UyNt)Je}f81bYVhB%+`{Ft&1*dFgf(hIqE z4%ysSyU8Vc+-uC&?{G}GOb`t<+V}V_Rp4v(#8kVi>&U?S0MQ80#p#IWT~;H0Ymf=W z;`4X&-A9B)Itjxi6#YcU2-O0;!xq{HAm7l`F0q=5W@KH?_O$1~Ujy6M7Yvi#we%V< ze^Y>BCLp&pL)BXMF4@^5Gk}qrr@S<^|Dk^{_&*#2n4XT6k-$ZDVSt2WuFoegFI)j2 z?W(uKb8}fnhz^u&#S@gVr-`PN@kX0EmVN|u#V3g)Q=RSDy3@`(NM2!{gqfoWNQIW3 zO;`yz2N+~~H_J`9+p!>vZ-#8XhwJs8n@-gM3T}Q6#~F)vLy|kS^w72`Bo9EHmR=R!LtxS#&-#Pht*7RyJp=~r-ru-Q?`Clk zy9j2EZNU$W1YXnAsa6rr36u$Qc=K;v-75jT$z*hyfkxb6B$8@y2#!3WHekxMcZW@7 zSNEG{9aPqZBS>VT)#(X<#jRa4TTWfU74Z~BysVv$>eljq8Pn(1@L>a$MLeueZB`Kv zWW))qxQM}&B2vI_F4fK!@p{P4lunTy&OgJJj%sb%btl#yT2@tc5foR-eL^s=lR6GM z^50-S8n7jhd+b`md|5DSoJx~;o;%qn1R9fA7MFMh& z+nm#6XPaMFQ&{W0vzQkwI$Jo}Hi)rbTx&?nuW(QtB)y;O4r8;5jRG$PihR0+1tdjhwOAvRNe-Q#4#wyK@F7mKGt*HRbWX44a387^p* z?(NC64Ik3mw!scU^e%!YvZ;TamI+E?Mr+Z%M>9DkyYb9ai7VdisNdfiwk=_Z$t;$D zO!%3&ZNP;}=dgv8to5H!tBMy--?J3{h~GWgsmgi+_Wj>b71%iK62gE_yUbcZmDHGE zck%O-7T3*&)AehNH*a$cU}Y6R`HY;D{7bbU`Yn0+4imx(Cx*{@j+ysyWTJ9u+8{H2 zby6s8_YiCDhlZ&pF^!3R7YKKFCgM0v7rNeGq-|lz5jbm!+XIQ1*?NUNb~DC+e}!Gs z4U^>Uz^#=VY*&g!ZHRvyTmi!(?Z#=!HIejM=WA}W)8aC10n;`K= zOt<7j?c3c{T`!@IE7pL;SU2qW(@Oq`s-BqFHQCvfzKg56VU)O^>-nps zbn;!fM}>ZnDxK;*-fyo@F1vAOoVk{cj|zOXgdU+lNqD7B9K^qoMvo#-41=EvKc*Zf zW80(JiKDK%s~n2~VFGsTN<(HTmz@#s*I#YUiN!NL)sr!ulfe_;CWYoklY|y;KPSHQ zeoR~dS(k+mKnG2dR8_*}d=ULpye*0CNNJKN=fjkk2x|ON_jc75;x`Z5y>qZ`` z-@nx8z8%%uQPsy$f6$A>{VgsccywTuME!sgf%RkaUQ^Y$6-yTeITeyHcm>wqP?m`gcmz>6m9w(f(1sD-V2XL?=Ba{AiK>`3d*ROl8k9TJk{Id2=|;@V zt=2o<##Rv}s*}6PY-4(=ebV#rPJK)T!mvL000g1*C^r)QJ55kRd~tCU71Zd_pkE+@ za$Sg%e{XX97~aszg%Yz9QViP#v&TWk7iUkyf2kH;Fy&py<)Q&^fY#@vwcN+SQKXMGnm zkS;4t2Oj0m${L?fP00%>Hqcy#BOi|6^gnCD^h^-$JMtS2Q8p*af;;DbDM(_|rLU8<(;-i2EuL}I+ zYo#5*{kP6K;D5S3PM?{(jg2tAhaz?v-)6Yya;2a0e8;6ar!-ppwFSp(&02Dk030(s zsJ~AWQ=|htbqJDhK)&<6oqmMk(4S}Sq1=%Wa0UGnv()GWP6%>N-N_HWFV9^@2dGWb z_stlWOS`j^L^7%d(tEt|b#Uet&@!i5-^3+1X*wZT!7jIcf-xVV#LU^_7NuLNdpQ=O zmWg3`{-tvx>lXb})q9-p7)6>4nLV?N-y;n}s=^zlYUED{CTPjZnM6cnrSDxrpYxxW zUL?&A&y?Zwev4YGni)%IncUEaxfQBU$EXzjS(8g*jQWP0lYN^u--&&w{cz5f8(#-l zwmw=6$xS-A#Q!X!Mm3x{o*wr-(%OPiKp&S?3EC&Jb}-@(c-g$5oAfQme&W*10Dd0F`}&^>*e>D+Q-{)mTMFp-nay8c zt2e3>2(AK)c15WuM#V7&rG4o5DnHmp21V%_3T#JI*FP1^G^be$0p&GCJSkfO?9)9x zeTOwerd0Nc#&7aeM@^=puGzD~s|?r_l$|{HEjl1AkD|v7pc*D;88IgHW7v9ms$u;CR0jb6 z_30*P|KN<^^}CFFM8%Vb6uV5V*|Bb8;a7QvCo;a>Lx33i;V8i>BI8#JfMc&K+67;a zH5v2o)q~0G@6`5jTe}so@R+*et~oI{C+u&9!NrtUR~|`9JN2Tmp8^0#7_i^j2UkGn zqE-)cKV>If-27FUJx_o6LH~;)XLPujASMQ-8N)OxUi^5WH}!Xywzq0RJlu~|+UqRRHGEdz^3Z~r1bk4_N2UgSRNzcjD+nbwW45FENtoi$l)ff)Q zXAo=t)?`;I5O6$VNGIQ@_m39