Update reflection database and enhance deduplication processes

- Updated the `rbx-dom` subproject to the latest commit for improved functionality.
- Enhanced the `/audit` command documentation to emphasize the importance of entering Plan mode before executing any actions.
- Improved the `atlas-dedup.mdc` file by adding details on user-initiated renames and ensuring ref path integrity during deduplication.
- Cleared out deprecated material color entries in `allValues.json` for better clarity.
- Updated `database.json` to reflect new properties and classes, including `AssetQualityService` and `BaseCoreGuiConfiguration`.
- Introduced new scripts for updating the reflection database, streamlining the process for generating necessary files.

These changes improve the overall functionality and maintainability of the project, ensuring better handling of deduplication and reflection updates.
This commit is contained in:
Joe 2026-02-17 07:12:00 -06:00
parent 85787f3452
commit 0afe6be441
12 changed files with 3220 additions and 283 deletions

View file

@ -1084,7 +1084,7 @@ impl JobThreadContext {
let mut dedup_metadata_updates: Vec<(Ref, PathBuf)> = Vec::new();
for &removed_id in &patch_set.removed_instances {
let (parent_ref, removed_fs_name) = {
let (parent_ref, removed_fs_name, removed_file_dir) = {
let Some(inst) = tree.get_instance(removed_id) else {
continue;
};
@ -1096,7 +1096,23 @@ impl JobThreadContext {
}
let parent = inst.parent();
let fs_name = tree.filesystem_name_for(removed_id);
(parent, fs_name)
// Derive the directory containing this file from its own
// instigating source path. This works even when the parent
// is a ProjectNode (where instigating_source is not a Path).
let file_dir = match source {
InstigatingSource::Path(p) => {
let fname = p.file_name().and_then(|f| f.to_str()).unwrap_or("");
if fname.starts_with("init.") {
// Directory-format: path is dir/init.luau,
// the containing dir is the grandparent
p.parent().and_then(|d| d.parent()).map(|d| d.to_path_buf())
} else {
p.parent().map(|d| d.to_path_buf())
}
}
_ => None,
};
(parent, fs_name, file_dir)
};
if parent_ref.is_none() {
@ -1157,15 +1173,11 @@ impl JobThreadContext {
let extension = removed_extension;
// Get the parent directory path
let parent_dir = {
let Some(parent) = tree.get_instance(parent_ref) else {
continue;
};
match &parent.metadata().instigating_source {
Some(InstigatingSource::Path(p)) => p.clone(),
_ => continue,
}
// Get the parent directory path (derived from the removed
// instance's own path, not the parent's instigating source,
// because the parent may be a ProjectNode).
let Some(parent_dir) = removed_file_dir.clone() else {
continue;
};
let action = compute_cleanup_action(
@ -1196,6 +1208,33 @@ impl JobThreadContext {
);
self.unsuppress_path(&to);
} else {
// Also rename the adjacent meta file if it exists
// (standalone files only; directory-format instances
// have init.meta.json5 inside the directory which
// moves automatically with the dir rename).
if from.is_file() || !from.exists() {
if let (Some(from_parent), Some(from_name), Some(to_name)) = (
from.parent(),
from.file_stem().and_then(|s| s.to_str()),
to.file_stem().and_then(|s| s.to_str()),
) {
let from_base = strip_script_suffix(from_name);
let to_base = strip_script_suffix(to_name);
let old_meta =
from_parent.join(format!("{}.meta.json5", from_base));
if old_meta.exists() {
let new_meta =
from_parent.join(format!("{}.meta.json5", to_base));
self.suppress_path_any(&old_meta);
self.suppress_path(&new_meta);
if fs::rename(&old_meta, &new_meta).is_err() {
self.unsuppress_path_any(&old_meta);
self.unsuppress_path(&new_meta);
}
}
}
}
// Update ref paths if the renamed instance has refs
let old_ref_segment =
from.file_name().and_then(|f| f.to_str()).unwrap_or("");

View file

@ -385,12 +385,22 @@ fn compute_change_count(
/// Count own property diffs between a snapshot and a tree instance.
/// Each differing property = +1. Tags and Attributes counted granularly.
/// Children count diff = +1.
///
/// Syncback strips default-valued properties from model/meta files, so
/// filesystem snapshots may omit properties that the tree (populated from
/// Studio via two-way sync) has at their class defaults. To avoid inflating
/// match costs with phantom diffs, properties present on only one side are
/// skipped when their value matches the class default.
fn count_own_diffs(snap: &InstanceSnapshot, inst: &InstanceWithMeta) -> u32 {
let mut cost: u32 = 0;
let snap_props = &snap.properties;
let inst_props = inst.properties();
let class_data = rbx_reflection_database::get()
.ok()
.and_then(|db| db.classes.get(snap.class_name.as_str()));
// Properties present on the snapshot side
for (key, snap_val) in snap_props.iter() {
if let Some(inst_val) = inst_props.get(key) {
@ -398,14 +408,24 @@ fn count_own_diffs(snap: &InstanceSnapshot, inst: &InstanceWithMeta) -> u32 {
cost += 1;
}
} else {
cost += 1; // Missing on tree side
let is_default = class_data
.and_then(|cd| cd.default_properties.get(key.as_str()))
.is_some_and(|default| variant_eq(snap_val, default));
if !is_default {
cost += 1;
}
}
}
// Properties present only on the tree side
for key in inst_props.keys() {
for (key, inst_val) in inst_props.iter() {
if !snap_props.contains_key(key) {
cost += 1;
let is_default = class_data
.and_then(|cd| cd.default_properties.get(key.as_str()))
.is_some_and(|default| variant_eq(inst_val, default));
if !is_default {
cost += 1;
}
}
}

View file

@ -226,7 +226,7 @@ fn compute_children_patches(
let instance_children = instance.children().to_vec();
let snapshot_children = take(&mut snapshot.children);
// Use the 3-pass matching algorithm instead of greedy name+class.
// Use the recursive change-count scoring + greedy assignment matching algorithm.
let match_result = match_forward(snapshot_children, &instance_children, tree);
// Matched pairs: recursively compute patches.

View file

@ -408,6 +408,9 @@ fn compute_change_count(
/// Count own property diffs between two WeakDom instances (flat, non-recursive).
/// Each differing property = +1. Children count diff = +1.
/// Does NOT include hash fast-path (handled by `compute_change_count`).
///
/// Properties present on only one side are skipped when their value matches
/// the class default (see `src/snapshot/matching.rs` for full rationale).
fn count_own_diffs(new_ref: Ref, old_ref: Ref, new_dom: &WeakDom, old_dom: &WeakDom) -> u32 {
let new_inst = match new_dom.get_by_ref(new_ref) {
Some(i) => i,
@ -420,19 +423,33 @@ fn count_own_diffs(new_ref: Ref, old_ref: Ref, new_dom: &WeakDom, old_dom: &Weak
let mut cost: u32 = 0;
let class_data = rbx_reflection_database::get()
.ok()
.and_then(|db| db.classes.get(new_inst.class.as_str()));
for (key, new_val) in new_inst.properties.iter() {
if let Some(old_val) = old_inst.properties.get(key) {
if !variant_eq(new_val, old_val) {
cost += 1;
}
} else {
cost += 1;
let is_default = class_data
.and_then(|cd| cd.default_properties.get(key.as_str()))
.is_some_and(|default| variant_eq(new_val, default));
if !is_default {
cost += 1;
}
}
}
for key in old_inst.properties.keys() {
for (key, old_val) in old_inst.properties.iter() {
if !new_inst.properties.contains_key(key) {
cost += 1;
let is_default = class_data
.and_then(|cd| cd.default_properties.get(key.as_str()))
.is_some_and(|default| variant_eq(old_val, default));
if !is_default {
cost += 1;
}
}
}