From e37945e3066309796b594e13132f85680c0fa00e Mon Sep 17 00:00:00 2001 From: kdletters Date: Tue, 4 Aug 2026 13:48:58 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=8F=AF=E8=B0=83=E7=94=A8?= =?UTF-8?q?=E5=9B=9E=E8=B0=83=E4=B8=8E=E8=A7=86=E8=A7=89=E8=84=9A=E6=9C=AC?= =?UTF-8?q?=E5=8F=AF=E8=BE=BE=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 合并条件表达式两端可调用身份并保留成员 bind 赋值结果 穿透回调调用括号并校正 Promise 回调参数槽 使用原始大小写脚本计算视觉资产语义可达性 补充回归测试与 Runtime 技术决策文档 --- .../runtime_protocol/autonomous_completion.rs | 292 ++++++++++++++---- .../shared-memory/decision-log.md | 1 + ...案】AI游戏创作智能体App实施计划-2026-06-24.md | 1 + 3 files changed, 242 insertions(+), 52 deletions(-) diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/autonomous_completion.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/autonomous_completion.rs index bb54d9a0d..6d49e4db4 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/autonomous_completion.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/autonomous_completion.rs @@ -1358,6 +1358,11 @@ enum JavascriptCallableValue { source: JavascriptSymbolId, captured_at: usize, }, + Member { + receiver: JavascriptSymbolId, + name: String, + captured_at: usize, + }, } impl JavascriptMethodReceiverOwnerCollector<'_> { @@ -1445,40 +1450,64 @@ impl JavascriptMethodReceiverOwnerCollector<'_> { } } - fn callable_value( + fn callable_values( &self, expression: &JavascriptExpression<'_>, position: usize, - ) -> Option { + ) -> Vec { match expression { JavascriptExpression::FunctionExpression(_) | JavascriptExpression::ArrowFunctionExpression(_) => { let span = expression.span(); - Some(JavascriptCallableValue::Direct(( + vec![JavascriptCallableValue::Direct(( span.start as usize, span.end as usize, - ))) - } - JavascriptExpression::Identifier(_) => { - self.referenced_symbol(expression) - .map(|source| JavascriptCallableValue::Alias { - source, - captured_at: position, - }) + ))] } + JavascriptExpression::Identifier(_) => self + .referenced_symbol(expression) + .map(|source| JavascriptCallableValue::Alias { + source, + captured_at: position, + }) + .into_iter() + .collect(), JavascriptExpression::ParenthesizedExpression(parenthesized) => { - self.callable_value(&parenthesized.expression, position) + self.callable_values(&parenthesized.expression, position) + } + JavascriptExpression::ConditionalExpression(conditional) => { + let mut values = self.callable_values(&conditional.consequent, position); + values.extend(self.callable_values(&conditional.alternate, position)); + values } JavascriptExpression::CallExpression(call) => call .callee .as_member_expression() .filter(|member| member.static_property_name() == Some("bind")) - .and_then(|member| self.referenced_symbol(member.object())) - .map(|source| JavascriptCallableValue::Alias { - source, - captured_at: position, - }), - _ => None, + .into_iter() + .flat_map(|bind| { + if let Some(source) = self.referenced_symbol(bind.object()) { + return vec![JavascriptCallableValue::Alias { + source, + captured_at: position, + }]; + } + bind.object() + .as_member_expression() + .and_then(|member| { + self.referenced_symbol(member.object()) + .zip(member.static_property_name().map(str::to_string)) + }) + .map(|(receiver, name)| JavascriptCallableValue::Member { + receiver, + name, + captured_at: position, + }) + .into_iter() + .collect() + }) + .collect(), + _ => Vec::new(), } } @@ -1495,19 +1524,29 @@ impl JavascriptMethodReceiverOwnerCollector<'_> { ) { return; } - let value = self.callable_value(expression, position); - self.callable_events - .entry(symbol_id) - .or_default() - .push(JavascriptAliasEvent { + let values = self.callable_values(expression, position); + let events = self.callable_events.entry(symbol_id).or_default(); + if values.is_empty() { + events.push(JavascriptAliasEvent { position, scope: javascript_alias_scope_at(self.ranges, position), - value, + value: None, conditional: javascript_position_is_conditionally_executed( self.conditional_ranges, position, ), }); + } else { + events.extend(values.into_iter().map(|value| JavascriptAliasEvent { + position, + scope: javascript_alias_scope_at(self.ranges, position), + value: Some(value), + conditional: javascript_position_is_conditionally_executed( + self.conditional_ranges, + position, + ), + })); + } } fn receiver_values( @@ -1613,9 +1652,9 @@ impl<'a> VisitJavascript<'a> for JavascriptMethodReceiverOwnerCollector<'_> { initializer, JavascriptExpression::FunctionExpression(_) | JavascriptExpression::ArrowFunctionExpression(_) - ) && self - .callable_value(initializer, declarator.span.start as usize) - .is_some() + ) && !self + .callable_values(initializer, declarator.span.start as usize) + .is_empty() { self.record_callable_assignment( symbol_id, @@ -1690,6 +1729,9 @@ fn javascript_expression_is_known_event_target( scoping: &JavascriptScoping, ) -> bool { match expression { + JavascriptExpression::ParenthesizedExpression(parenthesized) => { + javascript_expression_is_known_event_target(&parenthesized.expression, scoping) + } JavascriptExpression::Identifier(identifier) => { javascript_identifier_is_unresolved_known_global( identifier, @@ -1722,6 +1764,7 @@ fn javascript_known_callback_argument_indices( ) -> &'static [usize] { const NONE: &[usize] = &[]; const FIRST: &[usize] = &[0]; + const FIRST_AND_SECOND: &[usize] = &[0, 1]; const SECOND: &[usize] = &[1]; let unresolved_global = |identifier: &oxc_ast::ast::IdentifierReference<'_>| { identifier @@ -1729,7 +1772,11 @@ fn javascript_known_callback_argument_indices( .get() .is_some_and(|reference_id| scoping.get_reference(reference_id).symbol_id().is_none()) }; - if let JavascriptExpression::Identifier(identifier) = &call.callee { + let mut callee = &call.callee; + while let JavascriptExpression::ParenthesizedExpression(parenthesized) = callee { + callee = &parenthesized.expression; + } + if let JavascriptExpression::Identifier(identifier) = callee { if !unresolved_global(identifier) { return NONE; } @@ -1739,15 +1786,23 @@ fn javascript_known_callback_argument_indices( _ => NONE, }; } - let Some(member) = call.callee.as_member_expression() else { + let Some(member) = callee.as_member_expression() else { return NONE; }; let Some(name) = member.static_property_name() else { return NONE; }; + let mut receiver = member.object(); + while let JavascriptExpression::ParenthesizedExpression(parenthesized) = receiver { + receiver = &parenthesized.expression; + } if matches!(name, "catch" | "finally" | "then") { - return if matches!(member.object(), JavascriptExpression::ImportExpression(_)) { - FIRST + return if matches!(receiver, JavascriptExpression::ImportExpression(_)) { + if name == "then" { + FIRST_AND_SECOND + } else { + FIRST + } } else { NONE }; @@ -1756,7 +1811,7 @@ fn javascript_known_callback_argument_indices( name, "every" | "filter" | "find" | "forEach" | "map" | "reduce" | "some" ) { - return if matches!(member.object(), JavascriptExpression::ArrayExpression(_)) { + return if matches!(receiver, JavascriptExpression::ArrayExpression(_)) { FIRST } else { NONE @@ -1771,10 +1826,10 @@ fn javascript_known_callback_argument_indices( | "setTimeout" ) { let known_receiver = if name == "addEventListener" { - javascript_expression_is_known_event_target(member.object(), scoping) + javascript_expression_is_known_event_target(receiver, scoping) } else { matches!( - member.object(), + receiver, JavascriptExpression::Identifier(identifier) if javascript_identifier_is_unresolved_known_global( identifier, @@ -1854,6 +1909,23 @@ impl JavascriptFunctionInvocationCollector<'_, '_> { *captured_at, visiting, )), + JavascriptCallableValue::Member { + receiver, + name, + captured_at, + } => { + for owner in self.receiver_owners_for_symbol( + *receiver, + *captured_at, + &mut BTreeSet::new(), + ) { + indices.extend(self.method_indices_for_owner( + owner, + name, + &mut BTreeSet::new(), + )); + } + } } } } @@ -2737,11 +2809,12 @@ fn game_index_visibly_uses_visual_asset( let Ok(html) = std::str::from_utf8(html) else { return false; }; - let content = strip_art_reference_comments(html).to_ascii_lowercase(); - if !content.contains(&asset_path) { + let original_content = strip_art_reference_comments(html); + let heuristic_content = original_content.to_ascii_lowercase(); + if !heuristic_content.contains(&asset_path) { return false; } - let markup = strip_script_blocks(&content); + let markup = strip_script_blocks(&heuristic_content); let mut tag_cursor = 0; while let Some(start_offset) = markup[tag_cursor..].find('<') { @@ -2806,28 +2879,31 @@ fn game_index_visibly_uses_visual_asset( }) else { return false; }; - let identifiers = canvas_visual_identifiers(&content, &markup, &asset_path); + let identifiers = canvas_visual_identifiers(&heuristic_content, &markup, &asset_path); if identifiers.is_empty() { return false; } - let javascript = executable_javascript_from_html(&content); + let original_javascript = executable_javascript_from_html(&original_content); + let heuristic_javascript = original_javascript.to_ascii_lowercase(); let mut significant_draws = 0usize; - let function_ranges = named_javascript_function_ranges(&javascript); + let function_ranges = named_javascript_function_ranges(&original_javascript); let mut draw_cursor = 0; - while let Some(offset) = javascript[draw_cursor..].find("drawimage(") { + while let Some(offset) = heuristic_javascript[draw_cursor..].find("drawimage(") { let call = draw_cursor + offset; let arguments_start = call + "drawimage(".len(); draw_cursor = arguments_start; - if position_is_inside_javascript_string(&javascript, call) - || !javascript_position_is_reachable(&javascript, &function_ranges, call) + if position_is_inside_javascript_string(&heuristic_javascript, call) + || !javascript_position_is_reachable(&original_javascript, &function_ranges, call) { continue; } - let Some(arguments_end) = javascript_call_arguments_end(&javascript, arguments_start) + let Some(arguments_end) = + javascript_call_arguments_end(&heuristic_javascript, arguments_start) else { break; }; - let arguments = split_javascript_arguments(&javascript[arguments_start..arguments_end]); + let arguments = + split_javascript_arguments(&heuristic_javascript[arguments_start..arguments_end]); if arguments .first() .is_some_and(|identifier| identifiers.contains(*identifier)) @@ -9961,6 +10037,33 @@ pub(in crate::agent) fn autonomous_game_build_completion_blocker_at_locked( mod visible_destination_tests { use super::*; + fn mixed_case_main_loop_html(invocation: &str) -> Vec { + format!( + "" + ) + .into_bytes() + } + + #[test] + fn visual_asset_reachability_keeps_original_identifier_case_for_called_main_loop() { + assert!(game_index_visibly_uses_visual_asset( + &mixed_case_main_loop_html("MainLoop"), + "assets/art-spec.png", + (64, 64), + VisualAssetUsageRequirement::CanvasDraw, + )); + } + + #[test] + fn visual_asset_reachability_does_not_merge_differently_cased_main_loops() { + assert!(!game_index_visibly_uses_visual_asset( + &mixed_case_main_loop_html("mainloop"), + "assets/art-spec.png", + (64, 64), + VisualAssetUsageRequirement::CanvasDraw, + )); + } + #[test] fn canvas_draw_destination_rejects_offscreen_and_unbounded_dynamic_coordinates() { let canvas = (320.0, 180.0); @@ -10183,6 +10286,29 @@ mod javascript_projection_reachability_tests { } } + #[test] + fn conditional_expression_preserves_both_possible_callables() { + for (content, sources) in [ + ( + "function first() { return import('./conditional-first.mjs'); } function second() { return import('./conditional-second.mjs'); } const start = flag ? first : second; start();", + ["./conditional-first.mjs", "./conditional-second.mjs"], + ), + ( + "function first() { return import('./assigned-first.mjs'); } function second() { return import('./assigned-second.mjs'); } let start; start = flag ? first : second; start();", + ["./assigned-first.mjs", "./assigned-second.mjs"], + ), + ] { + let ranges = named_javascript_function_ranges(content); + for source in sources { + let position = content.find(source).expect("find conditional callable import"); + assert!( + javascript_position_is_reachable(content, &ranges, position), + "both conditional callable branches must remain reachable: {source}" + ); + } + } + } + #[test] fn local_function_alias_preserves_the_call_edge() { let content = "function start() { return import('./aliased-function.mjs'); } const alias = start; alias();"; @@ -10246,6 +10372,26 @@ mod javascript_projection_reachability_tests { } } + #[test] + fn member_bind_results_remain_callable_after_declaration_or_assignment() { + for invocation in [ + "const bound = api.run.bind(api); bound();", + "let bound; bound = api.run.bind(api); bound();", + ] { + let content = format!( + "const api = {{ run() {{ return import('./member-bind.mjs'); }} }}; {invocation}" + ); + let ranges = named_javascript_function_ranges(&content); + let position = content + .find("./member-bind.mjs") + .expect("find bound member import"); + assert!( + javascript_position_is_reachable(&content, &ranges, position), + "a bound local member must preserve its callable identity: {invocation}" + ); + } + } + #[test] fn named_function_expression_uses_its_outer_binding_as_one_range() { let content = @@ -10353,11 +10499,13 @@ mod javascript_projection_reachability_tests { let content = format!( "function callback(){{return import('./promise-{api}-callback.mjs')}} function extra(){{return import('./promise-{api}-extra.mjs')}} import('./seed.mjs').{api}(callback, extra);" ); - assert_callback_reachability( - &content, - &[&format!("./promise-{api}-callback.mjs")], - &[&format!("./promise-{api}-extra.mjs")], - ); + let callback = format!("./promise-{api}-callback.mjs"); + let extra = format!("./promise-{api}-extra.mjs"); + if api == "then" { + assert_callback_reachability(&content, &[&callback, &extra], &[]); + } else { + assert_callback_reachability(&content, &[&callback], &[&extra]); + } } for api in [ "map", "filter", "every", "find", "forEach", "some", "reduce", @@ -10373,6 +10521,47 @@ mod javascript_projection_reachability_tests { } } + #[test] + fn controlled_callback_apis_unwrap_parenthesized_callees_and_receivers() { + assert_callback_reachability( + "function callback(){return import('./parenthesized-timer.mjs')} (setTimeout)(callback, 0);", + &["./parenthesized-timer.mjs"], + &[], + ); + assert_callback_reachability( + "function callback(){return import('./parenthesized-array.mjs')} ([1]).map(callback);", + &["./parenthesized-array.mjs"], + &[], + ); + assert_callback_reachability( + "function fulfilled(){return import('./parenthesized-import-fulfilled.mjs')} function rejected(){return import('./parenthesized-import-rejected.mjs')} (import('./seed.mjs')).then(fulfilled, rejected);", + &[ + "./parenthesized-import-fulfilled.mjs", + "./parenthesized-import-rejected.mjs", + ], + &[], + ); + } + + #[test] + fn promise_then_executes_both_callbacks_but_catch_and_finally_only_the_first() { + assert_callback_reachability( + "function fulfilled(){return import('./then-fulfilled.mjs')} function rejected(){return import('./then-rejected.mjs')} import('./seed.mjs').then(fulfilled, rejected);", + &["./then-fulfilled.mjs", "./then-rejected.mjs"], + &[], + ); + for api in ["catch", "finally"] { + let content = format!( + "function callback(){{return import('./{api}-callback.mjs')}} function extra(){{return import('./{api}-extra.mjs')}} import('./seed.mjs').{api}(callback, extra);" + ); + assert_callback_reachability( + &content, + &[&format!("./{api}-callback.mjs")], + &[&format!("./{api}-extra.mjs")], + ); + } + } + #[test] fn import_member_callback_demands_use_the_same_exact_slots() { let content = "import * as api from './api.mjs'; setTimeout(api.timer, api.delay); document.addEventListener(api.eventType, api.listener, api.options); import('./seed.mjs').then(api.fulfilled, api.rejected); [1].reduce(api.reducer, api.initialValue); setTIMEOUT(api.wrongCase); [1].foreach(api.wrongMethodCase);"; @@ -10381,7 +10570,7 @@ mod javascript_projection_reachability_tests { .import_member_calls .get("api") .expect("record controlled callback member demands"); - for callback in ["timer", "listener", "fulfilled", "reducer"] { + for callback in ["timer", "listener", "fulfilled", "rejected", "reducer"] { assert!( members .keys() @@ -10393,7 +10582,6 @@ mod javascript_projection_reachability_tests { "delay", "eventType", "options", - "rejected", "initialValue", "wrongCase", "wrongMethodCase", diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index 055081608..decd07a19 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -5986,6 +5986,7 @@ - JavaScript / ESM 深层可达性补充:constructor、`.call/.apply` 与受控 inline callback 建立真实 invocation;具名 function expression 不再生成遮蔽外层 binding 的重叠节点,普通 inline function / arrow 未被执行时保持不可达。受控 callback API 名大小写敏感,并以精确参数索引建立执行边:timer、microtask、RAF、Promise 与数组迭代取第一个参数,`addEventListener` 取第二个参数,delay、initial value、event type、options 和额外参数保持普通值。条件/循环赋值合并执行与跳过状态,conditional expression 合并各 owner,未知确定赋值显式 invalidation;已调用函数对外层 alias 的副作用按调用位置传播,`super` owner 固定在 class 定义点。恒假扫描先屏蔽 parser 识别的注释和 literal;恒假分支区间随单次函数可达性 analysis 预计算、排序合并并以借用二分索引查询,不再使用 thread-local 完整源码 key 或命中时 clone ranges。投影 canonical 根名避让两侧全部非 import binding,dynamic shorthand 保留原键,循环模块按相同原始声明去重,同名 dynamic export 不得拉入无引用本地声明。 - JavaScript / ESM 深层可达性补充:constructor、`.call/.apply` 与受控 inline callback 建立真实 invocation;具名 function expression 不再生成遮蔽外层 binding 的重叠节点,普通 inline function / arrow 未被执行时保持不可达。条件/循环赋值合并执行与跳过状态,conditional expression 合并各 owner,未知确定赋值显式 invalidation;已调用函数对外层 alias 的副作用按调用位置传播,`super` owner 固定在 class 定义点。恒假扫描先屏蔽 parser 识别的注释和 literal。投影 canonical 根名避让两侧全部非 import binding,dynamic shorthand 保留原键,循环模块按相同原始声明去重,同名 dynamic export 不得拉入无引用本地声明。 - JavaScript / ESM 构造、继承与 callable 补充:`new` 沿冻结的 class owner 图执行本类显式 constructor、显式 `super()` 或隐式 derived constructor,并支持直接 class expression;instance / static 成员未 override 时继续沿 `extends` 链查找。普通嵌套 function 不继承 class `this`,arrow 保持词法 owner。`let binding; binding = function/arrow`、本地 function alias 和 Function.prototype `.bind()` 结果都建立 callable identity;`.call/.apply/.bind` 只有在 receiver 可解析为 callable 时采用 Function.prototype 语义,业务对象同名方法仍作为普通 receiver method 执行。 +- JavaScript / ESM callable、callback 与视觉可达性补充:callable conditional expression 合并两端全部身份;声明式与后续赋值式 member `.bind()` 都冻结成员 owner。受控 callback API 穿透 callee / receiver 外层括号,Promise `.then` 取 fulfilled 与 rejected 两个 callback 槽,`.catch/.finally` 仍仅取首槽。视觉资产路径与 `drawImage` 启发式可使用 ASCII 小写副本,但 AST、semantic binding 和函数可达性只解析原始大小写 JavaScript,大小写不同的 `MainLoop/mainloop` 不得合并。 - 浏览器因果:状态证据仍只冻结 trusted input listener 及其点击派生微任务内的变化;完整手势身份改由宿主在成功完成 Chromium 元素鼠标输入后调用隔离世界 finish。更早注册的 `window` capture listener 即使调用 `stopImmediatePropagation()` 也不能阻断探针自身的完成身份,页面脚本不能伪造 host finish,RAF / timer 继续不计入动作结果。 - 验证边界:Linux 定向回归覆盖目录相对读写与清理、祖先 symlink、CAS 安装后错误、九文件混合快照、Tetris AST 反例和七项真实 Chrome generic 试玩。Windows cfg 代码必须继续在真实 Windows CI / 发布构建验证;本地缺少 MinGW C compiler 时,安装了 Rust target 也不能把交叉 `cargo check` 失败误报为源码失败。 - JavaScript / ESM 循环与体积补充:投影声明按 `(origin module, original root binding)` 保存身份,canonical 重命名不能改写原始身份;删除回流声明后仍把 import 引用改接到 importer 已有 canonical,并给固定点保留“模块数 + 1”轮的产出与稳定确认预算,未收敛时失败关闭。inline module 先按浏览器可执行标签提取原文并计入与外部脚本共享的累计 `2 MiB` 源码预算,再做语法和语义校验;无效超限模块不能被 helper 静默过滤,无效小模块也明确失败关闭。 diff --git a/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md b/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md index d729a1028..1a3275449 100644 --- a/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md +++ b/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md @@ -836,6 +836,7 @@ game-project/ - imported member 只有作为受控 callback API 的参数时才建立执行 demand;日志、注册元数据等普通传参不能被当作调用。普通 member alias 必须同时支持声明 initializer 与后续赋值,`new ns.Game()` 的完整 constructor path 继续传播到实例 alias。局部对象、class、实例、`this` 和 `super` 的方法调用统一按 semantic receiver owner 匹配,禁止按末级方法名跨 owner 选中同名 decoy。 - member alias、局部 receiver 与动态 namespace 的 assignment 必须保留赋值位置和 enclosing function scope;函数体内的使用必须按真实 invocation position 选择当时已生效的事件,多次调用跨过赋值边界时合并全部可能 owner,恒假分支、未调用函数或调用之后的赋值不能倒灌覆盖更早使用点。`receiver = source` 必须冻结赋值完成时的 source owner,source 后续重赋值不能反向改写 receiver;调用事件按 JavaScript 的 callee、参数 / RHS、内层调用、外层调用 / 赋值顺序生效,`outer(inner())` 与 `receiver = mutate()` 不得按 AST 先序颠倒副作用。未知条件、循环以及 `switch case/default` 内赋值保留执行与跳过状态,conditional expression 保留全部可解析 owner,无法解析的确定重赋值显式 invalidation;已调用函数对外层 alias 的最终副作用按调用位置回写,但普通函数体内无条件 `return / throw` 之后的事件必须截断。被导出的 function、class/object member 还必须把模块初始化完成视为潜在外部调用时点,使声明之后生效的顶层赋值进入 demand,同时保留此前本地调用的旧状态。动态依赖传播继续以原始 owner 模块 AST span 为权威,不得因投影重排声明或省略独立赋值语句而重算出相反 alias。class owner 必须继续区分 static / instance,class expression 与实例化 alias 进入相同 receiver 图,`super` 的 parent owner 在 class 定义位置冻结。constructor、`.call()` 与 `.apply()` 都是实际 invocation;具名 function expression 只保留外层 binding 对应的单一 range,未被受控 API 执行的 inline function / arrow 保持不可达。恒真 / 恒假关键字只按 JavaScript 大小写识别,局部或参数遮蔽的 `undefined` 不得当作恒假;恒假分支识别必须先用 parser trivia 屏蔽注释和非执行 literal。callback API 只接受 semantic 未解析的已知全局调度函数或 AST 可证明的 literal array / dynamic import 调用;用户定义、导入或遮蔽的同名 `setTimeout / map / then` 不能触发参数执行推断。 - member alias、局部 receiver 与动态 namespace 的 assignment 必须保留赋值位置和 enclosing function scope;函数体内的使用必须按真实 invocation position 选择当时已生效的事件,多次调用跨过赋值边界时合并全部可能 owner,恒假分支、未调用函数或调用之后的赋值不能倒灌覆盖更早使用点。未知条件或循环内赋值保留执行与跳过两种状态,conditional expression 保留全部可解析 owner,无法解析的确定重赋值显式 invalidation;已调用函数对外层 alias 的最终副作用按调用位置回写,不能被 scope 隔离丢失。被导出的 function、class/object member 还必须把模块初始化完成视为潜在外部调用时点,使声明之后生效的顶层赋值进入 demand,同时保留此前本地调用的旧状态。动态依赖传播继续以原始 owner 模块 AST span 为权威,不得因投影重排声明或省略独立赋值语句而重算出相反 alias。class owner 必须继续区分 static / instance,class expression 与实例化 alias 进入相同 receiver 图,`super` 的 parent owner 在 class 定义位置冻结;`new` 必须覆盖显式 `super()`、隐式 derived constructor 和直接 class expression,未 override 的 instance / static 方法沿冻结的 `extends` 链查找。普通嵌套 function 拥有独立 `this`,只有 arrow 词法继承外层 owner。声明后通过 assignment 安装的 function / arrow、以及本地 function alias 和 `.bind()` 结果必须保留 callable identity。constructor、真正 Function.prototype 的 `.call()` / `.apply()` / `.bind()` 都是实际 invocation,但业务对象同名方法仍按 receiver method 解析;具名 function expression 只保留外层 binding 对应的单一 range,未被受控 API 执行的 inline function / arrow 保持不可达。恒假分支识别必须先用 parser trivia 屏蔽注释和非执行 literal。callback API 只接受 semantic 未解析的已知全局调度函数或 AST 可证明的 literal array / dynamic import 调用;用户定义、导入或遮蔽的同名 `setTimeout / map / then` 不能触发参数执行推断。 +- callable conditional expression 必须合并 consequent / alternate 的全部可调用身份;声明式和后续赋值式 `api.run.bind(api)` 都在绑定时冻结成员 owner 与方法名。受控 callback 识别穿透 callee 与 receiver 外层括号;Promise `.then` 的 fulfilled / rejected 两个参数都是 callback,`.catch` / `.finally` 仍仅取第一个。视觉资产启发式允许使用 ASCII 小写副本查找路径、`drawImage` 与图片变量,但函数调用图、semantic binding 和可达性必须始终解析原始大小写 JavaScript,禁止把 `MainLoop` 与 `mainloop` 合并。 - 泥点不足是确定性业务中断,不是瞬态 Provider 故障或未知副作用。钱包的 `泥点余额不足` 与 `可消费泥点不足:...` 两种领域文案统一映射为稳定原因 `mud-points-insufficient`,不得自动重试;即使 External Generation durable ledger 已存在,也必须落为 `failed`,不能误入 `needs-reconciliation`。game-chat 顶部状态、持久失败对话与 `【Supervisor 阶段记录】` 统一显示“泥点余额不足,本轮游戏生成已中断。请充值后发送“继续”,系统会从当前项目进度接着完成。”,并禁止透传 operationId、URL、路径、密钥或任意上游正文。 - tool-plan 成功响应落账前,对内置 Runtime 原生函数与 legacy wrapper 的合法、无重复 key JSON arguments 按工具 schema 的精确位置做项目路径 canonicalization:`file.*.path`、`project.patchset.changes[*].path`、`project.git_commit.paths[*]`、`command.*.cwd`、`image.inspect.paths[*]` 与 `canvas.asset_generate.outputPath` 若是当前项目根目录内的完整绝对路径,转换为 `/` 分隔的项目相对路径后再校验、持久化并执行;源码/叙述字段、任务产物描述、动态 MCP arguments 和项目外绝对路径不得改写,后两者继续由绝对路径门禁失败关闭。项目根只允许搜索/列举范围与命令 cwd 规范化为 `.`,不能成为文件目标。当前进程与重启恢复都必须从同一份规范化 handoff 重放,禁止分别执行原响应和持久响应。