Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
1a2dbdd
Port @artwyman's BatchDef, ItemInBatch, AllItemsInBatch predicates
arnaucube Nov 26, 2025
e8af4c6
add st_{batch/item}_def, st_item_in_batch methods; pending update macro
arnaucube Nov 26, 2025
e73940c
Adjust predicates
ax0 Nov 27, 2025
2288bb7
WIP
ax0 Nov 27, 2025
de016a6
Fix commitlib tests
ax0 Dec 1, 2025
e608127
Change itemdef statement method signature
ax0 Dec 1, 2025
3269e08
Update craftlib
ax0 Dec 2, 2025
936c403
Use HashMap for keys
ax0 Dec 2, 2025
ecb3cc9
Update app
ax0 Dec 2, 2025
e953251
small fixes, identify the cause of the error at the Synchronizer veri…
arnaucube Dec 2, 2025
c777b15
Fix committing & verifying
ax0 Dec 3, 2025
ffc39b9
Merge branch 'main' into multi-outputs
ax0 Dec 3, 2025
94d2a49
Form item POD in steps
ax0 Dec 3, 2025
345a8ad
Clean-up
ax0 Dec 3, 2025
85242b7
wip draft DisassembleStone into 2 outputs predicates
arnaucube Dec 3, 2025
2c1bf8c
fix DisassembleStone related predicates
arnaucube Dec 4, 2025
c5e0108
draft multi-outputs DisassembleStone rust statements methods
arnaucube Dec 4, 2025
7cde696
Complete statement methods
ax0 Dec 5, 2025
161b2ce
(wip) integrate multi-outputs crafting into the app_cli
arnaucube Dec 5, 2025
1d6d4de
add ui elems for multi-outputs DisassembleStone into Dust+Gem (not ye…
arnaucube Dec 5, 2025
1bb36f3
Fix CLI
ax0 Dec 8, 2025
9631c5c
Fix mock mode toggle
ax0 Dec 8, 2025
f853b36
Clippy
ax0 Dec 9, 2025
6a1dbbc
Code review
ax0 Dec 9, 2025
4c60ce2
Fix GUI flow
ax0 Dec 9, 2025
8e222e1
Remove print statements
ax0 Dec 9, 2025
6a2ddf4
fix multi-output outputs file naming (& ui icons), parametrize & redu…
arnaucube Dec 9, 2025
3876041
Fix CLI
ax0 Dec 11, 2025
d8075cb
Drop unnecessary PODs when crafting
ax0 Dec 12, 2025
7e2d79a
rm shell.nix file
arnaucube Jan 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,5 @@ The files in `app_gui/assets` are distributed under the Flaticon License:
- `uranium.png`: <a href="https://www.flaticon.com/free-icons/uranium" title="uranium icons">Uranium icons created by Freepik - Flaticon</a>
- `tomato.png`: <a href="https://www.flaticon.com/free-icons/tomato" title="tomato icons">Tomato icons created by PixelPerfect - Flaticon</a>
- `steel-sword.png`: <a href="https://www.flaticon.com/free-icons/steel-sword" title="steel sword icons">Steel sword icons created by Assia Benkerroum - Flaticon</a>
- `dust.png`: <a href="https://www.flaticon.com/free-icon/sand_4492623">Dust icon created by Freepik</a>
- `gem.png`: <a href="https://www.flaticon.com/free-icon/zirconia_6577837">Dust icon created by Freepik</a>
304 changes: 222 additions & 82 deletions app_cli/src/lib.rs

Large diffs are not rendered by default.

15 changes: 9 additions & 6 deletions app_cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ enum Commands {
Craft {
#[arg(long, value_name = "RECIPE")]
recipe: String,
#[arg(long, value_name = "FILE")]
output: PathBuf,
#[arg(long = "output", value_name = "FILE")]
outputs: Vec<PathBuf>,
#[arg(long = "input", value_name = "FILE")]
inputs: Vec<PathBuf>,
},
Expand Down Expand Up @@ -59,11 +59,11 @@ async fn main() -> anyhow::Result<()> {
match cli.command {
Some(Commands::Craft {
recipe,
output,
outputs,
inputs,
}) => {
let recipe = Recipe::from_str(&recipe)?;
craft_item(&params, recipe, &output, &inputs)?;
craft_item(&params, recipe, &outputs, &inputs)?;
}
Some(Commands::Commit { input }) => {
commit_item(&params, &cfg, &input).await?;
Expand All @@ -74,14 +74,17 @@ async fn main() -> anyhow::Result<()> {
// Verify that the item exists on-blob-space:
// first get the merkle proof of item existence from the Synchronizer
let item = RawValue::from(crafted_item.def.item_hash(&params)?);
let item_hex: String = format!("{item:#}");

// Single item => set containing one element
// TODO: Generalise.
let item_set_hex: String = format!("{item:#}");
let (epoch, _): (u64, RawValue) =
reqwest::blocking::get(format!("{}/created_items_root", cfg.sync_url,))?.json()?;
info!("Verifying commitment of item {item:#} via synchronizer at epoch {epoch}");
let (epoch, mtp): (u64, MerkleProof) = reqwest::blocking::get(format!(
"{}/created_item/{}",
cfg.sync_url,
&item_hex[2..]
&item_set_hex[2..]
))?
.json()?;
info!("mtp at epoch {epoch}: {mtp:?}");
Expand Down
Binary file added app_gui/assets/dust.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added app_gui/assets/gem.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
126 changes: 108 additions & 18 deletions app_gui/src/crafting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ pub enum Process {
Wood,
Axe,
WoodenAxe,
DisassembleStone,
Mock(&'static str),
}

Expand Down Expand Up @@ -97,6 +98,63 @@ IsWoodenAxe(item, private: ingredients, inputs, key, work, s1, wood1, wood2) = A
// prove the ingredients are correct.
IsWood(wood1)
IsWood(wood2)
)"#,
..Default::default()
};
static ref DISASSEMBLE_STONE_DATA: ProcessData = ProcessData {
description: "Disassemble Stone into Dust+Gem.",
input_ingredients: &["Stone", "Stone"],
outputs: &["Dust", "Gem"],
predicate: r#"
// inputs: 2 Stones
StoneDisassembleInputs(inputs, private: s1, stone1, stone2) = AND(
SetInsert(s1, {}, stone1)
SetInsert(inputs, s1, stone2)

// prove the ingredients are correct
IsStone(stone1)
IsStone(stone2)
)

// outputs: 1 Dust, 1 Gem
StoneDisassembleOutputs(batch, keys,
private: k1, dust, gem, _dust_key, _gem_key) = AND(
HashOf(dust, batch, "dust")
HashOf(gem, batch, "gem")
DictInsert(k1, {}, "dust", _dust_key)
DictInsert(keys, k1, "gem", _gem_key)
)

// helper to have a single predicate for the inputs & outputs
StoneDisassembleInputsOutputs(inputs, batch, keys) = AND (
StoneDisassembleInputs(inputs)
StoneDisassembleOutputs(batch, keys)
)

StoneDisassemble(batch, keys, work,
private: inputs, ingredients) = AND(
BatchDef(batch, ingredients, inputs, keys, work)
DictContains(ingredients, "blueprint", "dust+gem")

StoneDisassembleInputsOutputs(inputs, batch, keys)
)

// can only obtain Dust from disassembling 2 stones
IsDust(item, private: batch, ingredients, inputs, keys, key, work) = AND(
HashOf(item, batch, "dust")
DictContains(keys, "dust", key)
Equal(work, {})

StoneDisassemble(batch, keys, work)
)

// can only obtain Gem from disassembling 2 stones
IsGem(item, private: batch, ingredients, inputs, keys, key, work) = AND(
HashOf(item, batch, "gem")
DictContains(keys, "gem", key)
Equal(work, {})

StoneDisassemble(batch, keys, work)
)"#,
..Default::default()
};
Expand Down Expand Up @@ -400,6 +458,7 @@ impl Process {
Self::Wood => Some(Recipe::Wood),
Self::Axe => Some(Recipe::Axe),
Self::WoodenAxe => Some(Recipe::WoodenAxe),
Self::DisassembleStone => Some(Recipe::DustGem),
Self::Mock(_) => None,
}
}
Expand All @@ -410,6 +469,7 @@ impl Process {
Self::Wood => &WOOD_DATA,
Self::Axe => &AXE_DATA,
Self::WoodenAxe => &WOODEN_AXE_DATA,
Self::DisassembleStone => &DISASSEMBLE_STONE_DATA,
Self::Mock("Destroy") => &DESTROY_DATA,
Self::Mock("Tomato") => &TOMATO_DATA,
Self::Mock("Steel Sword") => &STEEL_SWORD_DATA,
Expand Down Expand Up @@ -461,7 +521,7 @@ impl Verb {
],
Self::Craft => vec![Axe, WoodenAxe, Mock("Tree House")],
Self::Produce => vec![Mock("Tomato"), Mock("Steel Sword")],
Self::Disassemble => vec![Mock("Disassemble-H2O")],
Self::Disassemble => vec![DisassembleStone, Mock("Disassemble-H2O")],
Self::Destroy => vec![Mock("Destroy")],
}
}
Expand Down Expand Up @@ -489,8 +549,8 @@ pub struct Crafting {
pub selected_action: Option<&'static str>,
// Input index to item index
pub input_items: HashMap<usize, usize>,
pub output_filename: String,
pub craft_result: Option<Result<PathBuf>>,
pub outputs_filename: Vec<String>,
pub craft_result: Option<Result<Vec<PathBuf>>>,
pub commit_result: Option<Result<PathBuf>>,
}

Expand Down Expand Up @@ -677,11 +737,20 @@ impl App {

self.crafting.selected_action = selected_action;

// NOTE: If we don't show filenames in the left panel, then we shouldn't ask for a
// filename either.
if self.crafting.output_filename.is_empty() {
self.crafting.output_filename =
format!("{:?}_{}", process, self.items.len() + self.used_items.len());
// prepare the outputs names that will be used to store the outputs files
let process_outputs = process.data().outputs;
if self.crafting.outputs_filename.is_empty() {
self.crafting.outputs_filename = process_outputs
.iter()
.enumerate()
.map(|(i, process_output)| {
format!(
"{}_{}",
process_output,
self.items.len() + self.used_items.len() + i
)
})
.collect();
}

ui.add_space(8.0);
Expand Down Expand Up @@ -711,11 +780,15 @@ impl App {
});

if button_craft_clicked {
if self.crafting.output_filename.is_empty() {
if self.crafting.outputs_filename.is_empty() {
self.crafting.craft_result = Some(Err(anyhow!("Please enter a filename.")));
} else {
let output =
Path::new(&self.cfg.pods_path).join(&self.crafting.output_filename);
let outputs_paths = self
.crafting
.outputs_filename
.iter()
.map(|output| Path::new(&self.cfg.pods_path).join(output))
.collect();
let input_paths = (0..inputs.len())
.map(|i| {
self.crafting
Expand All @@ -738,7 +811,7 @@ impl App {
params: self.params.clone(),
pods_path: self.cfg.pods_path.clone(),
recipe,
output,
outputs: outputs_paths,
input_paths,
})
.unwrap();
Expand All @@ -749,10 +822,23 @@ impl App {
}

if button_commit_clicked {
if self.crafting.output_filename.is_empty() {
if self.crafting.outputs_filename.is_empty() {
self.crafting.commit_result = Some(Err(anyhow!("Please enter a filename.")));
} else if self.crafting.craft_result.is_none()
|| self.crafting.craft_result.as_ref().unwrap().is_err()
{
self.crafting.commit_result = Some(Err(anyhow!(
"The item(s) must first be successfully crafted."
)));
} else {
let input = Path::new(&self.cfg.pods_path).join(&self.crafting.output_filename);
let first_output_filename = &self
.crafting
.craft_result
.as_ref()
.unwrap()
.as_ref()
.unwrap()[0];
let input = Path::new(&self.cfg.pods_path).join(first_output_filename);
self.task_req_tx
.send(Request::Commit {
params: self.params.clone(),
Expand All @@ -764,11 +850,15 @@ impl App {
}

if button_craft_and_commit_clicked {
if self.crafting.output_filename.is_empty() {
if self.crafting.outputs_filename.is_empty() {
self.crafting.commit_result = Some(Err(anyhow!("Please enter a filename.")));
} else {
let output =
Path::new(&self.cfg.pods_path).join(&self.crafting.output_filename);
let outputs_paths = self
.crafting
.outputs_filename
.iter()
.map(|output| Path::new(&self.cfg.pods_path).join(output))
.collect();
let input_paths = (0..inputs.len())
.map(|i| {
self.crafting
Expand All @@ -792,7 +882,7 @@ impl App {
cfg: self.cfg.clone(),
pods_path: self.cfg.pods_path.clone(),
recipe,
output,
outputs: outputs_paths,
input_paths,
})
.unwrap();
Expand Down
32 changes: 21 additions & 11 deletions app_gui/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,10 @@ impl eframe::App for App {
if let Ok(res) = self.task_res_rx.try_recv() {
match res {
Response::Craft(r) => {
if let Ok(entry) = &r {
self.load_item(entry, false).unwrap();
if let Ok(entries) = &r {
entries
.iter()
.for_each(|entry| self.load_item(entry, false).unwrap());
} else {
log::error!("{r:?}");
}
Expand All @@ -66,21 +68,23 @@ impl eframe::App for App {
log::error!("{e:?}");
}
// Reset filename
self.crafting.output_filename = "".to_string();
self.crafting.outputs_filename = vec![];
self.crafting.commit_result = Some(r);
}
Response::CraftAndCommit(r) => {
if let Ok(entry) = &r {
self.load_item(entry, false).unwrap();
if let Ok(entries) = &r {
entries
.iter()
.for_each(|entry| self.load_item(entry, false).unwrap());
} else {
log::error!("{r:?}");
}
self.refresh_items().unwrap();
self.crafting.input_items = HashMap::new();
// Reset filename
self.crafting.output_filename = "".to_string();
self.crafting.outputs_filename = vec![];
self.crafting.craft_result = None;
self.crafting.commit_result = Some(r);
self.crafting.commit_result = r.map(|entries| Ok(entries[0].clone())).ok();
}
Response::Null => {}
}
Expand Down Expand Up @@ -227,10 +231,12 @@ impl App {
ui.horizontal(|ui| {
ui.set_min_height(32.0);
// Mock toggle taken into account.
for verb in Verb::list()
.into_iter()
.filter(|v| self.mock_mode || v == &Verb::Gather || v == &Verb::Craft)
{
for verb in Verb::list().into_iter().filter(|v| {
self.mock_mode
|| v == &Verb::Gather
|| v == &Verb::Craft
|| v == &Verb::Disassemble
}) {
if ui
.selectable_label(Some(verb) == self.crafting.selected_verb, verb.as_str())
.clicked()
Expand Down Expand Up @@ -280,6 +286,10 @@ impl App {
egui::include_image!("../assets/tomato.png")
} else if name.starts_with("Steel Sword") {
egui::include_image!("../assets/steel-sword.png")
} else if name.starts_with("Dust") {
egui::include_image!("../assets/dust.png")
} else if name.starts_with("Gem") {
egui::include_image!("../assets/gem.png")
} else {
egui::include_image!("../assets/empty.png")
})
Expand Down
Loading