Smart FDC 战术层增强:任务优先级、移动目标提前量、炮击顺序规划、装填恢复、唯一任务序号 - #31
Conversation
Seven adversarial auditors compared v1 against the legacy diff; all findings folded in. Three declared deviations from legacy behavior: UTF-8 BOM discipline (no mojibake), cancellations now recorded in RecentTasks, and the reflection-contract note on CoroutineLock.Acquire overload ambiguity. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sequencing/log-order/threshold precision from the second adversarial pass, the ResolveElevation liveness exception, the FcsModule TaskContext carve-out, and the DisplayName()/ProgressText HUD contracts. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Seven Opus implementers rebuilt the enhancements from REQUIREMENTS.md alone (no access to the legacy diff); UTF-8 BOM discipline applied repo-wide; build clean at 0 errors / 0 warnings in Logic. Adversarial verification (7 comparators + reflection-contract check against the legacy behavior) found 4 low regressions - all fixed - and 12 accepted deviations, archived in REQUIREMENTS.md Appendix D. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 469c11a997
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| // Take the gun slot first and leave the queue only after that succeeded: an external reader must | ||
| // never observe a frame in which the task is neither pending nor on a gun. | ||
| if (!RemovePendingTask(task)) | ||
| MelonLogger.Warning($"[FCS Dispatch] admitted T{task.targetId} was no longer present in pending queue"); | ||
| MelonLogger.Warning($"[FCS Dispatch] admitted #{task.serial} was no longer present in pending queue"); |
There was a problem hiding this comment.
Revalidate tasks before admitting materialized plans
When CancelPendingBySerial or SweepExpiredTasks removes a task while this planning coroutine is suspended in MaterializeCandidate, the stale assignment still reaches AddPlan; this failed RemovePendingTask is only logged after the gun slot has already been assigned. Consequently, a task that was reported as cancelled/expired and recorded in RecentTasks can become active and eventually fire. Confirm the task is still pending and not failed before creating/admitting its plan, and discard or restart stale materialization otherwise.
Useful? React with 👍 / 👎.
| if (!ReferenceEquals(_current, plan) || !IsActive(plan) || plan.Failed) | ||
| yield break; | ||
| if (_fcs.Turret.LastRotationSucceeded) | ||
| appliedAzimuth = plan.Task.angel; |
There was a problem hiding this comment.
Reconcile the armed follower after an azimuth re-lay
When a same-bearing follower has already been armed during a manual fire wait and the current moving target then triggers this re-lay, the shared turret rotates but the follower remains armed. Pulling the shared trigger can therefore fire the follower at the current task's new bearing rather than its own target. After a successful re-lay, re-evaluate the follower against the effective bearing and clear its ready/armed state if it is now outside SameAzimuthToleranceDegrees.
Useful? React with 👍 / 👎.
| result => { | ||
| LastCardRequestResult = $"{request.CardId}: {result} @{FcsRuntimeClock.Now:F0}"; | ||
| MelonLogger.Msg($"[FCS] console card request {request.CardId} -> {result}"); |
There was a problem hiding this comment.
Make every card result token unique
If two identical card requests fail on an immediate path such as card not found or card has no DraggableItem, both callbacks can run within the same rounded second and produce exactly the same LastCardRequestResult. The external poller detects completions only by string inequality, so the later request then has no observable result and its caller can wait indefinitely. Include a monotonically increasing request/completion identifier rather than relying only on F0 time.
Useful? React with 👍 / 👎.
| var requiredCharge = BallisticCalculator.MinimumCharge(urgent.distance); | ||
| FirePlan? victim = null; | ||
|
|
||
| // Left before right, replacing only on a strictly lower priority, so equal priorities pick the left gun. | ||
| foreach (var plan in new[] { _leftPlan, _rightPlan }) | ||
| { | ||
| if (plan == null | ||
| || plan.Task.priority >= urgent.priority | ||
| || ReferenceEquals(_current, plan) | ||
| || ReferenceEquals(_fireWaitOwner, plan) | ||
| || plan.ShotObserved | ||
| || plan.Shell != urgent.bulletType | ||
| || plan.Charge < requiredCharge) |
There was a problem hiding this comment.
Refresh an urgent target before choosing a transferable charge
EnqueueTask attempts urgent preemption before the planning round calls RefreshPendingSolutions, so urgent.distance can still describe the unled target or an aim computed from an old firing origin. If motion lead or origin re-survey moves the target across a 5 km charge boundary, this check can preempt a plan whose committed charge becomes insufficient as soon as planning refreshes the urgent task; the victim is disrupted while the urgent task remains pending instead of taking the freed gun. Evaluate the urgent task's late-bound solution before calculating requiredCharge, or otherwise use a charge requirement that accounts for that pending refresh.
Useful? React with 👍 / 👎.
…ult uniqueness, stale preemption solution - Discard materialized plans whose task was cancelled/expired mid-round (and skip them in the pending-reset sweep - a second resurrection hole found while fixing the first). - Disarm a same-bearing armed follower when a manual-wait re-lay moves the shared turret outside SameAzimuthToleranceDegrees; new TriggerConsole.Disarm(side) primitive, follower task unaffected. - LastCardRequestResult gains a monotonic completion counter so identical same-second results stay distinguishable to pollers. - TryPreemptForUrgent refreshes the urgent task's late-bound solution before computing the transferable-charge requirement. Spec sections (16, 8.5, 15.2/17.13, 5.5, appendix C) updated in step. Verified via an equivalent out-of-repo compile (0 errors) - the local game install was removed after archival, taking the Il2Cpp reference assemblies with it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Codex 的四条意见全部采纳,已在 e0f1fd2 修复:
REQUIREMENTS.md 相应章节已同步。 🤖 Generated with Claude Code |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e0f1fd2f03
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| @@ -540,6 +888,79 @@ private bool CanFollowerArm(FirePlan current, FirePlan follower, out float azimu | |||
| return azimuthDelta <= SameAzimuthToleranceDegrees; | |||
There was a problem hiding this comment.
Compare followers against the effective bearing
When either task's bearing changes during the new execution-stage tracking refresh, these immutable FirePlan.Azimuth values remain at their planning-time bearings. In particular, the current plan can complete its pre-fire azimuth correction before opening the wait, after which this check still approves a follower whose original bearing matched even though the turret now points elsewhere; pulling the shared trigger then fires that follower off-target. Use the current turret/applied bearing and the follower's refreshed task bearing for this authorization.
Useful? React with 👍 / 👎.
| task.motionVelLocalPerSec = task.hasMotion | ||
| ? Vector3.Lerp(task.motionVelLocalPerSec, sampled, VelocityLowPassFactor) | ||
| : sampled; | ||
| _entitySamples[task.trackEntityId] = (local, now); |
There was a problem hiding this comment.
Share the sampled velocity between tasks
When multiple pending tasks track the same entity ID, the first task processed updates this shared sample timestamp, so every following task in the same refresh sees dt < 0.5s and leaves its own motionVelLocalPerSec unchanged. With the stable queue iteration order, a later task can remain at zero velocity through every refresh and be planned or armed without lead while the first task tracks correctly. Cache the fitted velocity with the entity sample or sample each entity once and propagate the model to all matching tasks.
Useful? React with 👍 / 👎.
|
|
||
| if (preFireSolve.Ok) | ||
| { | ||
| appliedDistance = plan.Task.distance; |
There was a problem hiding this comment.
Advance the range baseline only after elevation succeeds
If the pre-fire elevation command times out or otherwise leaves LastElevationSucceeded false, this assignment still records the refreshed range as applied. For an unchanged tracked range or a static agent re-aim, the manual-wait condition subsequently sees no range delta and never retries, while the trigger path proceeds with the old elevation (including immediate AutoFire). Move the baseline update after successful SetElevation; the manual-relay path has the same premature update pattern.
Useful? React with 👍 / 👎.
| if (plan == null | ||
| || plan.Task.priority >= urgent.priority | ||
| || ReferenceEquals(_current, plan) | ||
| || ReferenceEquals(_fireWaitOwner, plan) |
There was a problem hiding this comment.
Exclude the armed follower from urgent preemption
When a same-bearing follower has armed itself during the current manual fire wait, it is neither _current nor _fireWaitOwner, so an arriving higher-priority task can select it as the victim here. ReleaseGunSlot clears only the logical ready state and never disarms the physical safety; the commander's subsequent shared-trigger pull can therefore fire the preempted round after its FirePlan has been discarded, leaving an uncontrolled shot with no task to settle. Treat _armedFollower as non-preemptable, or synchronously disarm it before releasing the slot.
Useful? React with 👍 / 👎.
| _fcs.MapTable.ApplyMotionModel(urgent); | ||
| _fcs.MapTable.RefreshSolution(urgent); | ||
|
|
||
| var requiredCharge = BallisticCalculator.MinimumCharge(urgent.distance); |
There was a problem hiding this comment.
Reject unreachable urgent tasks before preempting
For a refreshed urgent range above the maximum C6 reach of 30 km, MinimumCharge saturates at 6 rather than indicating failure, so this method can evict a lower-priority C6 plan. The next planning round then rejects the urgent task at the explicit task.distance > charge * 5 eligibility check, meaning the victim was disrupted without creating any usable firing opportunity. Check the maximum supported range before selecting or tearing down a victim.
Useful? React with 👍 / 👎.
| continue; | ||
| } | ||
|
|
||
| var plan = _fcs.Planner.CreatePlan(assignment.Planning, item.Candidate, commitAt); |
There was a problem hiding this comment.
Revalidate re-aimed tasks before admitting their plans
A pending task remains adjustable while MaterializeCandidate yields on the physical calculator, but this admission path verifies only cancellation/expiry. If the commander moves the aim across a charge boundary during that window, the candidate still carries the old charge and elevation while CreatePlan reads the task's new bearing and range; the now-insufficient charge can be physically committed, and both execution-stage elevation solves then fail without preventing the trigger path from firing the stale solution. Detect solution changes after materialization and rematch or rematerialize before admission.
Useful? React with 👍 / 👎.
| if (task.serial == 0) | ||
| task.serial = ++_serialCounter; |
There was a problem hiding this comment.
Reserve externally supplied serials in the counter
When a supported external caller pre-sets a positive serial, this branch preserves it but does not advance or otherwise reserve _serialCounter. For example, enqueueing external serial = 1 followed by an ordinary zero-serial task assigns #1 to both, after which cancellation and re-aim operations select whichever duplicate they encounter first and recent outcomes cannot distinguish the missions. Preserve the supplied value while also advancing the counter or rejecting an already-used serial.
Useful? React with 👍 / 👎.
为 Smart FDC 补齐一组"战术层"增强,全部在
IronNestFCS.Logic内、不改既有公开 API。这套功能已随一个 LLM 火控指挥官项目实战通关完整战役(演示:YouTube / Bilibili),随后按规格做了一次 clean-room 重写,即本 PR。功能
ArtilleryTask.priority(0–100,默认 50)。高优先级任务赢下配位、同批开火顺序、跨批次执行顺序;≥90 视为紧急——跳过凑对窗口,且可抢占一门"装载已匹配"的忙炮(被抢占任务无损退回队列)。仰角 = 距离km × 12 / 装药,60° 封顶,52 组台解验证残差 ±0.01°);重解仰角走解析式,零耗时不占弹道台,物理台只做兜底。#N:取代可回收的地图标记号,取消/改瞄/日志/HUD 全部以 serial 寻址;左右炮位在 HUD 以固定槽位 T9/T10 标示。validForSeconds>0 的任务超窗自动撤销(仅限仍在等待队列的)。AdjustTaskAim非阻塞改瞄、CancelPendingTask、RequestConsoleCard打孔卡代购队列(方位/距离/起始网格拨盘自动化)。质量与兼容
FcsLocalization双语通道。REQUIREMENTS.md,约 1800 行,经两轮共 135 条对抗性审计),再由未接触旧代码的实现者按规格重写,最后逐子系统对照实战版本做行为回归验证。构建 0 error / 0 warning(nullable 全开)。如希望拆分成更小的 PR(比如只要优先级/移动目标/恢复中的某几项),我可以按子系统拆——各功能在提交上是可分的。
🤖 Generated with Claude Code