Phase 8: stopped realms keep the progression their core last reported; smoke tool project command

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ДмитрийandClaude Sonnet 5.5 committed 2026-10-07 11:25:55 +05:00
1 parent 9009550510
commit c36aaeca74
3 files changed
+40 -2

No files matched your search

+9 -2
View File
@@ -56,7 +56,14 @@ fn with_profile(db: &Db, store: &mut Store, base: &ImportOptions, data_dir: &str
}
let registry = coa_core::portable::extension::ExtensionRegistry::new();
let mut ra = ra_port.and_then(|port| std::env::var("COA_RA_PASSWORD").ok().and_then(|pw| coa_core::ra::Ra::connect_to(port, ra_user, &pw).ok()));
let caps = realm::profile::probe_capabilities(db, Some(std::path::Path::new(data_dir)), ra.as_mut(), &registry).map_err(|e| e.to_string())?;
let mut caps = realm::profile::probe_capabilities(db, Some(std::path::Path::new(data_dir)), ra.as_mut(), &registry).map_err(|e| e.to_string())?;
if ra.is_none() {
let remembered = store.realm_profile(server).map_err(|e| e.to_string())?;
caps = realm::profile::with_remembered_progression(caps, remembered.as_ref()).map_err(|e| e.to_string())?;
if caps.progression.is_none() {
eprintln!("realm {server}: its progression (level cap) has never been read from a running core: start it once and run `capabilities` with --ra-port");
}
}
let change = store.set_realm_profile(server, &caps, if ra.is_some() { "live" } else { "offline" }).map_err(|e| e.to_string())?;
eprintln!("realm {server}: content profile {}{}", &caps.content_profile_hash[..16], if change.changed() { " (new or changed)" } else { "" });
Ok(ImportOptions { capabilities: Some(std::sync::Arc::new(caps)), extensions: Some(std::sync::Arc::new(registry)), ..base.clone() })
@@ -268,7 +275,7 @@ fn run() -> Result<(), String> {
_ if store.server_mappings(model.character_id).map_err(|e| e.to_string())?.iter().any(|m| &m.server_id == server) => coa_core::portable::compat::Operation::Update,
_ => coa_core::portable::compat::Operation::OfflineImport,
};
let report = coa_core::portable::compat::evaluate(&coa_core::portable::compat::Inputs { operation, model: &model, capabilities: &caps, knowledge: opts.knowledge.as_deref(), collections: &[], extensions: opts.extensions.as_deref(), projection_decider: opts.projection.is_some() || matches!(operation, coa_core::portable::compat::Operation::OnlineImport) });
let report = coa_core::portable::compat::evaluate(&coa_core::portable::compat::Inputs { operation, model: &model, capabilities: &caps, knowledge: opts.knowledge.as_deref(), collections: &[], extensions: opts.extensions.as_deref(), projection_decider: opts.projection.is_some() || matches!(operation, coa_core::portable::compat::Operation::OnlineImport | coa_core::portable::compat::Operation::RuntimeSession) && ra_port.is_some() });
println!("{} on {server} (content profile {}): {:?}", report.operation, &caps.content_profile_hash[..16], report.verdict());
for o in &report.outcomes {
println!(" {o}");
@@ -96,6 +96,21 @@ pub fn probe_capabilities(db: &Db, data_dir: Option<&Path>, ra: Option<&mut Ra>,
assemble(db, &schema, data_dir, report.as_ref(), registry)
}
/// A profile assembled without the realm's core (a stopped realm) knows nothing of its progression. The progression the realm's core last
/// reported live (remembered in the store) is the best there is: the profile gets it, and the core still refuses a character prepared for
/// another progression at login, so a realm whose rules changed since is caught, not trusted.
pub fn with_remembered_progression(offline: RealmCapabilities, remembered: Option<&RealmCapabilities>) -> Result<RealmCapabilities> {
let Some(progression) = remembered.and_then(|r| r.progression.clone()) else { return Ok(offline) };
if offline.progression.is_some() {
return Ok(offline);
}
let mut content = offline.content.clone();
if remembered.is_some_and(|r| r.content.supports(Feature::LevelProjection)) {
content.features.insert(Feature::LevelProjection);
}
RealmCapabilities::build(offline.core.clone(), content, Some(progression))
}
/// The table-level view an adapter needs to say whether its module is present, before any character is involved.
struct NoRealm<'a> {
probe: &'a SchemaProbe,
@@ -234,6 +234,22 @@ mod tests {
assert!(matches!(plan_with(&c, 3, Some(&unknown), None), Err(PortableError::ProgressionChanged(_))));
}
#[test]
fn a_stopped_realm_keeps_the_progression_its_core_last_reported() {
use super::super::profile::with_remembered_progression;
let live = caps(60, SIG);
let mut offline = live.clone();
offline.progression = None;
offline.content.features.remove(&Feature::LevelProjection);
let offline = RealmCapabilities::build(None, offline.content, None).unwrap();
assert!(offline.needs_reprobe());
let merged = with_remembered_progression(offline.clone(), Some(&live)).unwrap();
assert_eq!(merged.progression, live.progression);
assert!(merged.content.supports(Feature::LevelProjection));
assert!(with_remembered_progression(offline.clone(), None).unwrap().needs_reprobe(), "nothing remembered, nothing invented");
assert_eq!(with_remembered_progression(live.clone(), Some(&caps(70, &"ab".repeat(32)))).unwrap(), live, "a live profile keeps its own");
}
#[test]
fn a_query_job_names_its_character_and_its_hash() {
let c = geared_level_eighty();