This Windows repo is build-harness-only: the product source under
local/workspace/Horosa-Web-*/ (astrostudyui / astropy / astrostudysrv / vendor) is gitignored
and is wholesale-replaced from the Mac repo on every sync (re-clone Mac → copy Mac's Horosa-Web/ tree over the workspace, see .claude/skills/horosa-dev/SKILL.md gotcha #16). The Mac tree does not
contain the Windows-specific adaptations below, so every sync drops them — and a git reset --hard /
workspace wipe loses them with no git recovery (this is exactly what happened in the v2.5.0 "disaster").
So they live here, tracked in git. After each wholesale-replace, run:
bash windows-adaptations/apply.sh \
local/workspace/Horosa-Web-55c75c5b088252fbd718afeffa6d5bcb59254a0c \
tmp/mac-sync-<ver>/Horosa-Web # optional 2nd arg: Mac clone (for THIRD_PARTY_NOTICES.md)then cd desktop_installer_bundle && npm run selfcheck — the windows-ahead / ported-fix sentinels
gate must be 40/40. Every item below is guarded by a release_selfcheck.py sentinel, so a drop is
caught before release.
Every Windows-only change to the product source MUST ship all five layers, or the release gate fails:
- Patch/file in
windows-adaptations/patches/(marker-guarded diff) orfiles/(full copy) — the change itself, tracked in git. apply.shsection — re-applies it after every Mac wholesale-replace (idempotent: the marker grep makes it a no-op when already present).release_selfcheck.pysentinel — greps the live workspace file for the marker; a dropped change hard-fails the release (windows-ahead / ported-fix sentinelsgate). Backend (Java) markers double as rebuilt-jar content sentinels (SKILL gotcha #5: patching the source is NOT enough — the jar must be rebuilt).- SKILL.md note (
.claude/skills/horosa-dev, gotcha #16) — the sync playbook starts withapply.sh+selfcheck, so the next sync session re-applies everything before building. - Memory note — the assistant's persistent memory records the round's overlay additions, so even a fresh session knows they exist.
Tracked-harness changes (desktop_installer_bundle/electron/*, scripts/*) don't need layers 1–2 (git-safe or ships-in-exe), but DO need layer 3 (sentinel) — and gitignored harness files are additionally inventoried in HARNESS_MANIFEST.md (below) so a silent loss is detectable and recoverable from the shipped exe's app.asar.
electron-updater's blockmap differential download is only as good as the packaging structure lets it
be: measured on the old single-tar format, a version upgrade reused just 16.8% of the installer
(~622MB re-downloaded). The DELTA-V2 structure (per-file payload tree + Spring Boot exploded backend)
makes cross-version downloads track the true change instead of the total. These invariants are
self-enforcing — release_selfcheck.py gates (payload tree format + path budget,
installer size ceiling, differential efficiency) hard-fail a release that violates them, so any
future session/agent inherits the mechanism without needing to understand it.
The eight invariants:
- Payload = a file tree, NEVER a monolithic archive.
stage-runtime.cjspacksbuild/app-runtime-packed/asruntime/+project/+payload-manifest.json(format 2: per-filepath/size/sha256, payloadId = manifest hash). A tar/zip payload avalanches its whole compressed stream on any byte change → delta dies. (Contract-recorded exception: a subtree may be short-name-archived ONLY to satisfy invariant 4's path budget, and must be noted here.) - Backend ships in Spring Boot's official exploded form (
bundle/astrostudyboot-exploded/: BOOT-INF/lib per-jar + deterministichorosa-classes.jar+ classpath.idx + MANIFEST.MF + the staging-written.horosa-exploded.jsonmarker withbundleFingerprint). The 324MB fat jar, the machine-built uber jar, and CDS.jsaarchives must NOT enter the payload — each would change wholesale every backend release and ruin the delta.build-uber-jar.py extract(build machine) andmerge-tree(user machine) share one merge implementation, proven CONTENT_EQUIVALENT to the legacy fat→uber path (118,728 entries, 0 mismatches). - Deterministic derived artifacts — and the payload is
.pyc-FREE.horosa-classes.jaris built with sorted entries + zeroed zip timestamps + fixed compression (DETERMINISTIC_ZIP) so unchanged content is byte-identical across builds. Python bytecode caches (*.pyc,__pycache__/) must NEVER ship: they are volatile on the build machine (any pip / compileall / pytest activity rewrites thousands — measured 3.0.1→3.1.0: 7,640 changed.pyc= 140.7 MB of phantom diff that collapsed blockmap reuse to 15%).stage-runtime.cjsrunsprunePythonCaches()over BOTH the runtime tree and the project tree, and thepayload tree format + path budgetgate hard-fails on any.pycin the manifest. Users regenerate bytecode on demand (writable extracted runtime) andschedulePycCompileback-fills the full tree post-ready — steady-state performance is unchanged. electron-builder's app-64.7z is already differential-friendly by design (non-solid,-mtm/-mta/-mtc=off); do NOT changecompression/archive options without re-running the double-build determinism proof. - Path budget ≤160 chars for every payload-relative path (NSIS 3.0.4.1 has no long-path manifest
and installs via MAX_PATH-bound SHFileOperation; the budget leaves prefix headroom). Enforced at
staging (
assertPayloadPathBudget) AND at release (selfcheck gate). - Release hygiene: the previous release's
.exe+.blockmapassets stay published when N+1 ships (the updater derives the old blockmap URL by version substitution — never delete/draft the prior release first); all releases build on the same machine/OS (LZMA2 multithreading drifts bytes across machines — a cross-OS build measured ~97% delta on identical content); every release passes thedifferential efficiencygate (bump releases: download ≤ truly-changed-bytes ×1.5 +50MB; overwrites: informational;HOROSA_DELTA_BASELINE_RESET=1for declared format migrations). - Updater identity frozen:
updaterCacheDirName/ productName /perMachine:falsemust not change — the NSIS template self-seeds%LOCALAPPDATA%\<updaterCacheDirName>\installer.exeon every install, which is why even a manually-downloaded install gets a DIFFERENTIAL first auto-update. - The mechanism lives ONLY in the Windows harness + this overlay (stage-runtime.cjs, service-manager.js, build-uber-jar.py, delta-report.py, selfcheck — all HARNESS_MANIFEST-tracked) with ZERO structural coupling to the product source: the payload manifest scans whatever the tree contains. A Mac sync can add/rename/move/delete anything and the delta simply tracks it — and can never clobber the machinery itself.
Mac-sync playbook (when a sync trips a gate — full steps in SKILL.md「增量更新」):
- Path-budget gate trips → shorten/short-name-archive the offending subtree (invariant 1 exception, record it here).
- Size-ceiling gate trips (huge new assets) → the sanctioned escape hatch is
nsis-web + differentialPackage. - Staging explode asserts trip (Spring Boot layout changed upstream) → fix
extract_tree's asserts, or ship ONE release withHOROSA_SHIP_FAT_JAR=1(legacy tar+fat-jar format; sentinel-guarded emergency path, delta poor for that release only) and return to tree format next release. - After every Mac sync, run
python scripts/delta-report.pyagainst the live blockmap before releasing — the delta composition should match what the sync actually changed.
Round-3 note — the dominant Windows perf fix is NOT in this overlay. Measured: Windows Defender on-access scanning of the bundled runtime = ~500x I/O tax (2 MB/s) — the #1 cause of slow startup + slow compute on Windows (macOS has no such scanning). The fix is a consented Defender exclusion of the app's own runtime, implemented in the (gitignored, ships-in-exe) shell:
desktop_installer_bundle/electron/defender-exclusion.js(app first-run, UAC-consented, idempotent) +assets/installer.nsh(install-time, elevated best-effort). Not a product-source patch, so it is guarded byrelease_selfcheck.pysentinels rather than an apply.sh row.
| # | Target (in workspace) | What / Why | How restored |
|---|---|---|---|
| 1 | astrostudyui/scripts/umi-runner.js |
Windows-only. Mac's package.json build scripts use bash export NODE_OPTIONS=--openssl-legacy-provider && umi build, which fails on Windows cmd/PowerShell. This wrapper sets the env cross-platform and runs umi. build-renderer.cjs runs npm run build:file in this dir, so it's load-bearing. |
full copy in files/ |
| 2 | astrostudyui/scripts/loadCryptoDeps.js + scripts/vendor/ |
Windows-only. Crypto-dep shim + vendored js-rsa/node-forge used by the Windows build. |
full copy in files/ |
| 3 | astrostudyui/package.json (name + scripts) |
Keep Mac's v2.5.0 dependencies, but restore the Windows name (horosa-astrostudyui) + umi-runner-based scripts (start/build/build:file/postinstall). scrub-build-paths.js (strips the build machine's absolute path out of the bundle; umi bakes it into umi.*.js) and it is now part of the Windows chain too. Guards: sentinel on the WS package.json (umi-runner.js + scrub-build-paths.js dist-file) and the artifact-side negative gate dev-path bake-in scan, which now scans the shipped dist-file/ as well (a string sentinel proves the script is wired; only the artifact scan proves the output is clean). |
apply.sh merges Mac deps + files/astrostudyui/package.name-scripts.json |
| 4 | astropy/requirements.txt |
Strip flatlib==0.2.3.post3. Not on PyPI (it's Mac's local flatlib-ctrad2 setup.py version); on Windows flatlib is provided by the vendored flatlib-ctrad2 via sys.path injection in the service-manager bootstrap, NOT pip. The pin makes the embedded-python pip install -r requirements.txt fail resolution → installs nothing → chart service dead. |
apply.sh: sed -i '/^flatlib==/d' |
| 5 | THIRD_PARTY_NOTICES.md (workspace root) |
Mac keeps it at the repo root (sibling of Horosa-Web/), so a Horosa-Web-only replace misses it; stage-runtime.cjs hard-requires it at the workspace root or dist:win dies in stage:runtime. |
apply.sh copies from the Mac clone root |
| 6 | astrostudyui/src/utils/windowSizePersistence.js |
Add isDesktopShellWindow(win) (Electron mirror of Mac's isTauriWindow, via window.horosaDesktop) + skip web-layer window persistence in the desktop shell — Electron self-manages window bounds; web persistence caused startup resize jitter. |
patches/src__utils__windowSizePersistence.js.patch |
| 7 | astrostudyui/src/pages/index.js |
Add ensureField(flds,name) defensive guard + String()-coerce lat in changeCond — a restored/imported chart payload may omit a field or carry a numeric lat, which would crash lat.toLowerCase(). |
patches/src__pages__index.js.patch |
| 9 | astrostudyui/src/utils/perfFlags.js + components/planetarium/PlanetariumBabylon.js + components/xuanshi/{echartsCore.js (new), XuanShiCelestial.js, XuanShiMap.js} |
v3.0.1 perf — pure performance, ZERO functionality change; cross-platform. Planetarium render-loop gating: idle render-throttle (document.hidden — kills the software-render 60 fps core-peg that janks the whole app (visible+active+playing = identical). onMetrics→确定/commit never debounced → final result exact). echarts: full barrel import * as echarts from 'echarts' → modular echarts/core + echarts.use([...]) (玄学史 chunk slimming; charts byte-identical). Each gated by a perfFlags.js kill-switch. Recommend landing upstream in Mac (Mac tree is identical — no platform-specific reason); once Mac has them the marker guards make apply.sh a no-op (isLoopbackTarget precedent). |
files/ full-copy echartsCore.js + patches/src__utils__perfFlags.js.patch (marker planetariumRenderGatingEnabled) + patches/src__components__planetarium__PlanetariumBabylon.js.patch (marker perf:planetariumRenderGating) + patches/src__components__xuanshi__XuanShiCelestial.js.patch + patches/src__components__xuanshi__XuanShiMap.js.patch (marker ./echartsCore;刻意写成两个字面文件名而不是花括号通配 —— 五层契约交叉核对门按「补丁文件名字面出现」判定,通配写法会让它判为未登记) |
| 10 | astrostudyui/src/utils/perfFlags.js + components/ziwei/ZiWeiMain.js |
v3.0.1 perf round-2 — pure performance, ZERO functionality change; cross-platform. Deterministic technique-result cache: techniqueResultCacheEnabled() kill-switch + ZiWei natal /ziwei/birth fetch wrapped in cachedPost (same-params reuse + in-flight dedup, deep-clone per caller → mutation-safe) so switching to / oscillating 紫微 is instant on a repeat. Only deterministic pure-compute endpoints (no random casting / no "now" dependency). Recommend landing upstream in Mac. |
patches/src__utils__perfFlags.techniqueCache.js.patch (marker techniqueResultCacheEnabled, applied AFTER the planetarium perfFlags patch) + patches/src__components__ziwei__ZiWeiMain.js.patch |
| 11 | astrostudysrv/astrostudycn/.../controller/ChartController.java |
BACKEND — needs a jar rebuild. v3.0.1 perf round-2 B0: /chart per-segment timing (Python base / bazi+assemble / predictSign / predSync / total) written to the perf log — observation only, does NOT change compute, control flow, or the response. Makes the real "single-chart 2-3s" breakdown visible on the owner's machine to target follow-up compute optimizations. CHART_PERF_SEG_REV doubles as the rebuilt-jar sentinel marker. Recommend landing upstream in Mac. |
patches/astrostudycn__ChartController.java.patch (marker CHART_PERF_SEG_REV) |
| 12 | astrostudyui/src/utils/perfFlags.js + components/xuanshi/XuanShiMain.js |
v3.0.1 perf round-3 — pure timing, ZERO functionality change; cross-platform. First-load parallelization: firstLoadParallelEnabled() kill-switch + 玄学史 XuanShiMain.loadSummary fires its 4 independent fetches (总览/玄典/名家/事件) CONCURRENTLY instead of await-summary-then-3-serial → faster first open (summary still gates the overview render; results unchanged). Recommend landing upstream in Mac. |
patches/src__utils__perfFlags.firstLoadParallel.js.patch (marker firstLoadParallelEnabled, applied AFTER the techniqueCache perfFlags patch) + patches/src__components__xuanshi__XuanShiMain.js.patch |
| 13 | astrostudysrv/boundless/.../log/QueueLog.java |
BACKEND — needs a jar rebuild. v3.0.1 perf round-3: processLog did a Thread.getStackTrace() synchronously on the request thread for EVERY info/debug call, just to append a cosmetic (Class.method:line) suffix. Now OFF by default (returns the format unchanged); restore with -Dhorosa.queuelog.callerLocation=true. Read as a JVM system property (no Spring init-order dependency). Logging + results unchanged. horosa.queuelog.callerLocation doubles as the rebuilt-jar sentinel marker. Recommend landing upstream in Mac. |
patches/boundless__QueueLog.java.patch (marker horosa.queuelog.callerLocation) |
| 14 | astropy/astrostudy/jieqi/{YearJieQi.py, BirthJieQi.py} |
v3.0.1 perf ROUND-3 R1 — pure performance, ZERO functionality change; cross-platform. approach()'s convergence loop used to rebuild an entire flatlib Chart(...) on every iteration just to read Sun.lon + Sun.lonspeed — but a Chart constructs ~100 swe.calc_ut calls (all planets + houses + arabic parts). Per /jieqi/year that's 24 solar terms × 3–5 iterations = 7,200–12,000 wasted Swiss Ephemeris calls per request = ~30s. Fix: direct swe.sweObject(SUN, jd, SEDEFAULT_FLAG) — same underlying swe.calc_ut source, same {lon, lonspeed} dict values that the Chart path reads via getObject(SUN).lon. Convergence judgement, delta formula, Datetime.fromJD chain unchanged → result byte-identical. Golden-diff self-verify (24 terms + 100 random (year, zone, lat, lon) combos): max_jdn_diff = 0.000e+00, VERDICT = ALL_EQUAL, SPEEDUP = 21.6× baseline / 44.3× random. Kill-switch HOROSA_JIEQI_FAST_APPROACH=0. |
patches/astropy__jieqi__YearJieQi.fastApproach.py.patch + patches/astropy__jieqi__BirthJieQi.fastApproach.py.patch (marker HOROSA_JIEQI_FAST_APPROACH) |
| 15 | astrostudysrv/astrostudy/.../helper/ParamHashCacheHelper.java |
BACKEND — needs a jar rebuild. v3.0.1 perf ROUND-3 R2: /jieqi/year's local disk cache silently no-op'd every call — JieQiController.getYearParams() puts TimeZiAlg + PhaseType Enum instances into the response, canPersistLocal() sees non-primitive types → return false → saveToLocal() returns without writing anything. With Redis explicitly disabled on desktop (--paramhash.cache.redis.enable=false), every call was a full 30s recompute. ChartController had a private toPlainMap band-aid but JieQiController (and 8 other controllers) never got that fix. Root-cause generalization: promote to ParamHashCacheHelper.persistable(Object) public method (JSON round-trip via same Jackson jsonMapper as the response serializer; idempotent on already-plain trees; fail-safe returns raw value on any error), and call it centrally inside get() before saveToLocal/saveToRedis. All 11 controller call-sites (ChartController × 3, JieQiController × 2, LiuRengController × 2, IndiaChartController, BaZiBirthController, PaiBaZiController, AstroHelper) now benefit automatically; the bug cannot re-appear in future controllers. PARAMHASH_PERSISTABLE_REV doubles as the rebuilt-jar sentinel marker. |
patches/astrostudy__ParamHashCacheHelper.persistable.java.patch (marker PARAMHASH_PERSISTABLE_REV) |
| 16 | astropy/websrv/kentang/registry.py + astropy/astrostudy/xuanshi/__init__.py + astropy/websrv/webchartsrv.py |
v3.0.1 perf ROUND-3 R3 — pure performance, ZERO functionality change; cross-platform. mount_kentang_services() used to synchronously __import__ all 17 kentang service specs at server start, one of which (webxuanshisrv, added in v3.0.0) recursively pulls in xuanshi's 5 heavy submodules + a 35 MB public_data.sqlite + a 64 MB editorial.sqlite. On Windows this adds ~5–10s of Python cold-import to warm-start (v2.6.9 had no xuanshi module — this is the code-side reason "v2.6.9 was fast, v3.0.0/v3.0.1 got slower on the same machine"). Root-cause generalization: wrap all 17 specs in a _LazyMountedService proxy (CherryPy dispatch triggers __import__ on first request, memoized) — future new services also don't regress startup. Self-verify: mount_kentang_services() wall-clock went 12,018 ms → 0.2 ms (60,000×) and webxuanshisrv + all 7 xuanshi submodules disappear from the startup import tree. Plus a PEP 562 __getattr__ in xuanshi/__init__.py so any direct-attribute access from callers also stays lazy (belt-and-suspenders). Plus a background daemon thread (HorosaXuanShiWarmup) that sleeps 5 s past the visible-window paint then importlib.import_module('websrv.webxuanshisrv') so the first user click on the 玄学史 tab is warm — the Python-side dual of Round-2's chartProbeWarmupPromise in the shell. Kill-switches HOROSA_KENTANG_LAZY_MOUNT=0 / HOROSA_XUANSHI_LAZY_IMPORT=0 / HOROSA_XUANSHI_WARMUP=0. ROUND-4 expansion (same patch file): the warmup thread now ALSO materializes 玄学史's global_summary() once (first-click data cache), plus a staggered 7s sibling thread pre-imports 七政's webqizhengkinsrv (~2.4s cold streamlit import, HOROSA_QIZHENG_WARMUP=0), plus a generalized 9s thread pre-imports the remaining 16 lazy-mounted service modules one-by-one 0.8s apart (HOROSA_SERVICES_WARMUP=0) — so EVERY technique's first click is warm. All daemon + per-module try/except; byte-identical (same imports the lazy mount would run on first request, memoized in sys.modules). |
patches/astropy__kentang__registry.lazyMountAll.py.patch (marker HOROSA_KENTANG_LAZY_MOUNT) + patches/astropy__xuanshi__init.lazyImport.py.patch (marker xuanshi.lazyImport) + patches/astropy__webchartsrv.xuanshiWarmup.py.patch (apply.sh guard marker HOROSA_SERVICES_WARMUP — the newest marker, so a post-sync pristine file always re-applies the full set) |
| 17 | astrostudysrv/boundless/.../log/AppLoggers.java |
BACKEND — needs a jar rebuild. v3.0.1 perf ROUND-4 P0: the six programmatic appenders (error/warn/info/debug/perf/access) resolve their base dir via getStrSubstitutor().getVariableResolver().lookup("basedir"), which returns log4j2.xml's unsubstituted template literal ${env:HOME:-${sys:user.home}} at runtime. POSIX filesystems tolerate ${} as directory-name characters (so macOS/Linux "work", writing to a literal weird-named dir), NTFS rejects them → all six appenders fail at startup on Windows → startup error spam + the /chart per-segment perf log (#11) and user error logs are silently lost. Fix: resolveBaseDir() prefers the -Dhorosa.log.basedir system property the desktop launcher has always passed (previously read by nobody), falls back to the original lookup, and if that still contains an unresolved ${ falls back to user.home/.horosa-logs/astrostudyboot. Also guards changeLogFile()'s daily re-build to only touch appenders under this basedir layout (the old substring(basedir.length()+11) position math could corrupt/NPE XML-defined appenders → stop-without-replace). Without the -D, behavior is identical to before → server/mac deployments unchanged. log_basedir_v1 (HOROSA_LOG_BASEDIR_REV) doubles as the rebuilt-jar sentinel marker. |
patches/boundless__AppLoggers.logBasedir.java.patch (marker log_basedir_v1) |
| 18 | astrostudysrv/astrostudyboot/.../AstroStudyProgram.java |
BACKEND — needs a jar rebuild. v3.0.1 perf ROUND-4 P1: B0 timing showed 80% of the 9.7s first /chart is baziAssemble (7,781 ms) — a one-time cold cost (nongli year-table compute + class init + JIT) paid by whoever draws the first chart. Fix: baziAssembleWarmup CommandLineRunner (daemon thread) pre-runs one OnlyFourColumns construction + getNongli() with NOW as the synthetic natal (pre-fills the exact (year, zone) table a real chart needs), result discarded, failures silent. Kill-switch HOROSA_CHART_WARMUP=0. bazi_warmup_v1 (HOROSA_CHART_WARMUP_REV) doubles as the rebuilt-jar sentinel marker. |
patches/astrostudyboot__AstroStudyProgram.baziWarmup.java.patch (marker bazi_warmup_v1) |
| 19 | astropy/astrostudy/perchart.py + astropy/astrostudy/guostarsect/guo74.py + flatlib-ctrad2/flatlib/ephem/ephem.py |
v3.0.1 perf ROUND-5 — pure performance, ZERO functionality change; cross-platform. Within ONE /chart request the same pure computations ran 2–3×: the 67-fixed-star batch (getStars + getFixedStars each recompute; flatlib rescans sefstars.txt per star), the 28-宿 batch (adjusted + raw + guo74's per-star loop = 3 fetches), sunrise solving (up to 50 iteration Charts, run 2–3×), surroundAttacks / getMutuals (re-run by besiegementDetail). Fix: per-instance request-scoped memos on the chart object (reset in reinit(), zero cross-request sharing; fetch+relocate cached ONCE — relocate is a +180° mutation so the cache stores the FINAL list; raw vs adjusted 28-宿 kept in SEPARATE caches to prevent mutation cross-contamination) + guo74 reads the raw batch instead of 28 individual chart.getFixedStar() calls + a bounded thread-safe LRU (8 entries) in flatlib ephem.py keyed on (IDs, jd, pos, height, flags, sidereal-context) — star batches don't depend on house system/orbs/aspects, so "change a setting, redraw the same chart" reuses them; deepcopy on store AND hit so consumers mutate private copies (LRU kill-switch HOROSA_STAR_LRU=0). Golden: 4 chart variants (standard/south/斗柄/七政) byte-identical under PYTHONHASHSEED=0; repeat chart 617–747 → 443–504 ms; settings-change star segment 379–480 → 183–236 ms. Recommend landing upstream in Mac. |
patches/astropy__perchart.chartMemo.py.patch (marker _getFixedStars67Cached) + patches/astropy__guostarsect__guo74.su28Batch.py.patch (marker getRawFixedStarSu28Cached) + patches/flatlib__ephem.starLru.py.patch (marker HOROSA_STAR_LRU) |
| 20 | astropy/astrostudy/jieqi/NongLi.py + astropy/astrostudy/jieqi/BirthJieQi.py (expanded) |
v3.0.1 perf ROUND-5 — same HOROSA_JIEQI_FAST_APPROACH lever as #14, two more sites. NongLi.approach() (朔/new-moon + solar-term candidate solving for the lunar-calendar year table) rebuilt a full Chart per iteration just to read Sun/Moon longitudes → direct swe.sweObject; convergence judgement unchanged → 4 golden years (incl. BC-500) byte-identical via jsonpickle; fresh-year table 1,445–2,460 → 113–194 ms (the dominant slice of a first-ever bazi chart after #18's warmup misses). BirthJieQi 卯时/ascendant solving only reads ASC → slim Chart(..., IDs=[SUN], needpars=False) (_ascChart); 3 golden dates identical, 398–490 → 30–36 ms. Recommend landing upstream in Mac. |
patches/astropy__jieqi__NongLi.fastApproach.py.patch (NEW, marker _JIEQI_FAST_APPROACH) + patches/astropy__jieqi__BirthJieQi.fastApproach.py.patch (regenerated; guard marker unchanged) |
| 21 | astropy/websrv/webchartsrv.py (expanded, same patch as #16) |
v3.0.1 perf ROUND-5 — Python service start & observation. ① HOROSA_CETIAN_LAZY (default on): lazy-mount /cetian like the kentang 17 (historically webcetiansrv pulled streamlit at top level = 49% of the startup import wall; the v3.1.0 upstream streamlit stub removed the biggest chunk, the lazy mount still skips the engine's own cold import — the warmup thread back-fills it in idle → 天文地占 first click stays warm); ② HOROSA_PY_CHART_TIMING=1 (default off): pure-observation CHART_PY_PERF init/build/encode per-segment timing for /chart. RETIRED in v3.1.0 (FengShui precedent — upstream rewrite obsoletes the Windows-ahead fix): the ROUND-5 HOROSA_PY_WARMUP_BLOCKING/_run_startup_warmups post-bind warmup deferral is superseded by the upstream 启动就绪门 (STARTUP_GATE + background _run_warmups thread + a CherryPy before_handler that holds business POSTs until warmup completes; kill-switch HOROSA_PY_WARMUP_SYNC=1) — strictly better (same earliest-serviceable-moment guarantee, formal sid_mode concurrency protection), so the overlay no longer carries it; sentinels now guard the upstream gate markers instead. |
patches/astropy__webchartsrv.xuanshiWarmup.py.patch (same §14/§16 guard marker HOROSA_SERVICES_WARMUP; the one patch carries the full warmup+lazy+timing set, regenerated against the v3.1.0 base) |
| 23 | astrostudyui/src/components/homepage/PageHeader.js |
v3.1.0 官方仓库链接平台化。 Mac v3.1.0 在「关于」面板挂了法律文档(docs/legal)与「官方下载渠道」(releases)链接,但共享源里 HOROSA_OFFICIAL_REPO 硬编码为 Mac 仓库 URL —— Windows 用户点击会落到错误平台的下载页/仓库。Windows 侧改指本仓库(…-Windows);docs/legal 已随 v3.1.0 同步 tracked 进本仓库,两条派生链接(/tree/main/docs/legal、/releases)同构有效。功能行为不变(仍是浏览器外开)。 |
patches/src__components__homepage__PageHeader.officialRepo.js.patch(marker=Windows 仓库 URL 子串 comprehensively-improved-Windows) |
| 22 | astrostudysrv/astrostudy/.../helper/NongliHelper.java + astrostudysrv/astrostudycn/.../model/OnlyFourColumns.java |
BACKEND — needs a jar rebuild. v3.0.1 perf ROUND-5 B-F3: ① NongliHelper persists a per-DAY nongli row to the external cache for every never-seen date (one read that basically always misses + one write, per new date); the day row is a pure derivation of the in-memory month tables, so recomputing is byte-identical. Desktop shell injects HOROSA_NONGLI_DAY_PERSIST=0 to skip the day-level read/write; year-table persistence (the expensive compute) is untouched; env absent → original behavior → server/mac unchanged. ② OnlyFourColumns.forwardDirect had a stray debug System.out.println firing per bazi chart (stdout pipe noise, zero response bytes) — deleted; comment marker quiet_println_v1 is the sentinel. |
patches/astrostudy__NongliHelper.dayPersist.java.patch (marker nongli_day_persist_v1) + patches/astrostudycn__OnlyFourColumns.quietPrintln.java.patch (marker quiet_println_v1) |
| 8 | astrostudysrv/boundless/.../net/http/HttpUriRequestHystrixCommand.java |
BACKEND — needs a jar rebuild. Windows issue #14: the embedded JVM was tunnelling its internal 127.0.0.1:8899 chart-service calls through the system proxy. doCmd set RequestConfig.setProxy(getHttpHost()) unconditionally; with the launcher's -Djava.net.useSystemProxies=true (for AI reachability, #9) getHttpHost() resolves the OS proxy, and Apache HttpClient applies an explicit proxy ignoring http.nonProxyHosts → Clash/v2ray (ubiquitous on CN Windows) mishandles 127.0.0.1 → 12–17 s stalls → "本地排盘服务未就绪". Fix: skip the proxy for loopback targets (isLoopbackTarget); external AI hosts still use it (#9 preserved). The shared frontend connection-refused retry (Mac 270eb01e, services/astro.js) only covers the restart/startup window — it does NOT cover this proxy stall, which is the Windows-specific half of #14. |
patches/boundless__HttpUriRequestHystrixCommand.java.patch |
| 25 | astrostudyui/src/models/astro.js + src/services/astro.js + src/components/comp/ChartFormData.js + src/components/astro/AstroFormComp.js + src/utils/perfFlags.js(并入累积补丁)+ src/pages/index.js(并入累积补丁) |
PERF-R7 T-6 预测性预计算 + P1-5/T-3(纯性能,功能零变化;跨平台,建议上游化 Mac)。 ①astro/precomputeFetch:排盘抽屉表单编辑期 300ms 防抖,把「提交会发出的同一份参数」提前只暖 services 层 chartMem/在途缓存(不落 state、不动 UI、失败静默)→ 点提交时 *fetch 命中/join,点击→显示≈渲染耗时;参数构造与 *fetch 逐字节一致(date/time/ad/zone/cid+includePrimaryDirection),lon/lat 未填不预取。②fetchChart 只缓存有效盘(chartMem_valid_only_v1:错误信封不进缓存,对既有路径也是净改善)。③ChartFormData scheduleLivePrecompute(setValue/changeBirth 汇流点防抖、unmount 清理、clickOk 取消)+ AstroFormComp onLivePrecompute 挂线(prop 可选=未挂载方零影响)。④pages/index.js:P1-5 切页 User-Timing 打点(horosa:tab:*:refresh-start)+ T-3 idle 预载 1s 起跑。⑤perfFlags 新增 speculativePrecomputeEnabled(kill-switch,默认开)。注意:PERF-R7 起 perfFlags 的三段链式补丁(planetarium/techniqueCache/firstLoadParallel)与 pages/index.js 补丁均改为「Mac 基线→现状」累积全量补丁(守卫 marker 各取最新:speculativePrecomputeEnabled / refresh-start),旧链文件已删。 |
patches/src__models__astro.precomputeFetch.js.patch(marker precomputeFetch)+ patches/src__services__astro.chartMemValidOnly.js.patch(marker chartMem_valid_only_v1)+ patches/src__components__comp__ChartFormData.livePrecompute.js.patch(marker scheduleLivePrecompute)+ patches/src__components__astro__AstroFormComp.livePrecompute.js.patch(marker onLivePrecompute)+ patches/src__utils__perfFlags.speculativePrecompute.js.patch(累积)+ patches/src__pages__index.js.patch(累积) |
| 24 | astropy/websrv/kentang/kinastro_common.py + astropy/websrv/kentang/registry.py (expanded #16) + astropy/websrv/webchartsrv.py (expanded #16/#21) + astropy/tests/{test_kentang_lazy_mount_selfheal.py (expanded), test_streamlit_stub_hygiene.py (new)} |
v3.2.1 太乙/三式合一 404 事故的根治三层(跨平台 bug —— 建议上游化到 Mac)。 根因(运行进程实测钉死):_ensure_streamlit_stub 的 _StubModule.__getattr__ 对任意属性返回 _noop 函数——包括 __file__。真 astropy(PyPI 天文库,kintaiyi/太乙的依赖)导入期会 inspect.getmodule() 遍历 sys.modules 逐模块读 __file__(期望 str)→ 桩在场时 function.endswith 炸 AttributeError → kintaiyi 导入永久失败;CherryPy dispatcher 用 getattr(node,name,None) 走树,把 AttributeError 吞成「无此路由」→ /taiyi/pan 整进程瞬时静默 404。七政(+14s)/玄学史(+10s)预热先导入 kinastro adapter 注入桩、用户首点太乙必在其后 → Windows 必现;Mac 用户「先开七政再开太乙」同样触发。修复:①根因 桩对 dunder 探测一律 AttributeError 拒答(hasattr(stub,'__file__')=False,inspect/pickle/copy 标准内省安全跳过;具名属性 no-op 语义不变;子桩补 __horosa_slim_stub__);②机制免疫 _LazyMountedService.__getattr__ 把任何加载失败转 KentangServiceLoadError(RuntimeError 子类,非 AttributeError)+ stderr 完整 traceback → 未来任何服务加载故障都是可诊断的 5xx,结构上不可能再静默 404;_import_kentang_service_module 保留首次失败异常链(first_error)供诊断;③顺序免疫 webchartsrv 预热线程最先缓存真 astropy 库(kentang_astropy_preimport_v1,兼把太乙首点最大一次性导入成本挪进空闲期)。门制度化:scripts/verify_kentang_services.py 升级为 spec 正序+逆序双通道(逆序=七政先于太乙,精确复刻生产事故顺序;两趟合计覆盖全部有序模块对)每次 dist:win 强制 18/18×2;回归 pytest 9 项(自愈 3 + 响亮失败 2 + 桩卫生 4,含子进程真 astropy 端到端)。 |
v3.2.2 起大部分上游化收敛(见 #26):registry 补丁已删(Mac 原样采纳+强化);kinastro 桩补丁 patches/astropy__kentang__kinastro_common.stubDunderGuard.py.patch 保留为 no-op 安全网(Mac 已带 stub_dunder_guard_v1,故 apply.sh 的 grep 守卫会自动跳过;留着是为了 Mac 万一回退时仍能自动还原);webchartsrv 补丁瘦身为唯一存活增强(见 #26);两个 Windows 回归测试文件被 Mac 五文件套件取代删除。 |
| 26 | astropy/websrv/webchartsrv.py(收敛后)+ astropy/websrv/kentang/registry.py(=Mac)+ astrostudycn/.../OnlyFourColumns.java(=Mac) |
v3.2.2 上游化收敛轮(isLoopbackTarget #8 同款)。 Mac v3.2.2 将 Windows 首创的整套 kentang 架构(惰性挂载 _LazyMountedService/自愈 _import_kentang_service_module/响亮失败 KentangServiceLoadError)原样上游化并强化(净化白名单 _PURGE_PROTECT_PREFIXES、HOROSA_KENTANG_LAZY 回退旗、显式 prewarm_kentang_services),并上游化了 quiet-println(WS-3b)、astropy 预导入(_warm_real_astropy)、错峰服务预热(→prewarm)、/chart 三段计时(→启动账本 _PY_CHART_TIMING)、idle 预载 1s(T-3)。Windows 存活增强只剩一项:xuanshi_summary_warmup_v1 —— kentang prewarm 之后、STARTUP_GATE 打开之后再物化 global_summary()(全表 SELECT+译名 join+celestial,冷 ~2.3s),玄学史首点秒开;门后执行=绝不延长业务请求等待。退役:HOROSA_CETIAN_LAZY(v3.1.0 桩后 cetian 饿加载已廉价,Mac 实测保持饿加载,收敛)。哨兵全部迁移至上游 marker。 |
patches/astropy__webchartsrv.xuanshiWarmup.py.patch(regenerated vs v3.2.2 基线;守卫 marker xuanshi_summary_warmup_v1);registry/OnlyFourColumns 零补丁(=Mac) |
| 27 | utils/idleWarmQueue.js + utils/perfFlags.js(累积)+ pages/index.js(累积)+ models/astro.js(累积)+ services/astro.js(累积)+ components/astro/IndiaChart.js + components/guolao/GuoLaoChartMain.js + components/direction/AstroDirectMain.js + components/germany/AstroMidpoint.js + utils/preciseCalcBridge.js + components/jieqi/JieQiChartsMain.js + idleWarmQueue 测试 |
PERF-R8 P0/P2/P3(纯前端、双闸、功能零降级;跨平台,建议上游化 Mac)。 P0 观测:排盘 refresh-end / render-complete / cache-hit User-Timing,与 refresh-start 配对把「点击→显示」分解为后端+网络 vs 前端渲染。P2 排盘后数据层空闲预热:idleWarmQueue 新增组式 API scheduleDataWarmGroup(generation 作废旧组+泵可重 arm,修复「启动时快照任务列表→排盘后注册即空转」的结构缺陷);排盘成功(chartId 变)后按当前盘预热 星运 /predict/pd(builder 抽纯函数 buildPrimaryDirectionRequestPure,warm 绝不 dispatch)/ 印占(buildIndiaWarmParams=fieldsToParams+默认 dashaSystem 补齐,否则 key 差一项必 miss)/ 七政本命(warmGuolaoNatal,禁 snapshot 入口、kinastro 样式跳过、流年含取现时禁入)/ 量化盘中点(warmGermanyMidpoint,口径=组件 live 路径≠AI 无头版)。全部走各技法自己导出的 builder+缓存入口 → key/body 与真实首点逐字节一致;silent:true;确定性端点白名单;重端点(/jieqi/year)不进默认组。P3:分至图 year±1 邻位预取(prefetchJieqiYearNeighbors,当前年 resolve 后才排、generation 门控防连点风暴、顺序发在途≤1)。闸:horosa.perf.idleWarmQueue(总)/horosa.perf.dataWarmTasks(细)/horosa.perf.neighborPrefetch。lazy-init A/B 满血实测不采纳(ON 中位 +86ms,成本从绑口搬进首跳);BeanTiming 观测器(jar 侧,HOROSA_BEAN_TIMING=1)为下一轮定点惰化取数。 |
apply.sh §23;全部经 regen_patch.py 再生 + round-trip 内建。逐个字面列出(交叉核对门 R3 按文件名字面出现判定,「见 apply.sh §23」这种指路写法会被判为未登记 —— 本行此前正是如此,由该门首跑抓出):patches/src__utils__idleWarmQueue.dataWarmGroup.js.patch · patches/src__utils__tests__idleWarmQueue.test.dataWarmGroup.js.patch · patches/src__components__astro__IndiaChart.warmParams.js.patch · patches/src__components__guolao__GuoLaoChartMain.warmNatal.js.patch · patches/src__components__direction__AstroDirectMain.warmPd.js.patch · patches/src__components__germany__AstroMidpoint.warmMidpoint.js.patch · patches/src__utils__preciseCalcBridge.neighborPrefetch.js.patch · patches/src__components__jieqi__JieQiChartsMain.neighborPrefetch.js.patch(累积补丁 src__utils__perfFlags.speculativePrecompute.js.patch / src__pages__index.js.patch / src__models__astro.precomputeFetch.js.patch / src__services__astro.chartMemValidOnly.js.patch 已在 #22/#25 各自登记) |
| 28 | start_horosa_local.sh + verify_horosa_local.sh |
Web 版启停三件套加固(跨平台,建议上游化 Mac;apply.sh §24)。 start:①Java spawn 前 env -u 剥离宿主 _JAVA_OPTIONS/JAVA_TOOL_OPTIONS/JDK_JAVA_OPTIONS/CLASSPATH(IDE/安卓工具链机器的全局注入会并入 JVM 参数;镜像桌面 sanitizeEmbeddedRuntimeEnv 语义);②Python 剥离 PYTHONHOME/PYTHONSTARTUP/PYTHONUSERBASE + -X utf8 CLI 旗(优先级高于任何继承环境,宿主 PYTHONUTF8=0 也压不掉;CJK 路径/非 UTF-8 码页机器行为一致);③三处 exit 3 前补中英自解释(手动运行者不再面对裸退出码);④就绪后 45s 空闲 pyc 后台预编译(HOROSA_WEB_PYC_PRECOMPILE=0 关;镜像桌面 schedulePycCompile,下次启动省 2-3s)。verify:file_mtime 的 stat -f 补 GNU -c 回退(此前 Git Bash 下恒回 0 → freshness 永远判旧);⑤ v3.3.4:上游新增 progress-aware 就绪门以 BSD stat -f%z 取日志字节数(Git Bash 恒败 → 进展指纹只剩账本行数一个弱信号)→ start 侧 _progress_fingerprint 同补 GNU -c%s 回退(marker 与 verify 同名)。未采纳并记录:就绪环并发早探与中断时落 CDS 档(需重构 Mac 生产启动器 trap/就绪环,风险>收益,留作上游建议)。 |
patches/start_horosa_local.webLauncherHardening.sh.patch(markers horosa_web_java_env_sanitize_v1/horosa_web_python_utf8_v1/horosa_web_pyc_precompile_v1/horosa_web_portable_stat_v1)+ patches/verify_horosa_local.portableStat.sh.patch(marker horosa_web_portable_stat_v1) |
| 29 | astrostudyui/src/components/common/__tests__/quickDockContract.test.js |
快捷功能坞契约测试跨平台修复(v3.3.1;跨平台 bug,建议上游化 Mac)。 Mac v3.3.1 新增的 QuickDockBar「全站快捷栏静态契约」源码扫描测试用 path.relative(COMPONENTS_DIR,f) 取相对路径,再与正斜杠字面量 WHITELIST/EXEMPT 比对;Windows 上 path.relative 返回反斜杠分隔 → .includes 恒 false → 白名单页(QuickDockBar / AstroChartMain / KinAstroMain)被误判 offender、契约两测假红(2 failed)。修:relPosix() 用 .split(path.sep).join('/') 归一 POSIX 分隔符再比对(macOS path.sep='/' 时为 no-op,行为逐字节不变;其余用 path.join 读文件的断言天然跨平台,无需改)。 |
patches/src__components__common__quickDockContract.pathsep.test.js.patch(marker horosa_win_pathsep_posix_v1 / relPosix) |
| 30 | astrostudyui/src/utils/__tests__/chartFreeContract.test.js |
chartFree 契约测试跨平台修复(v3.5.0;与 #29 同类,跨平台 bug,建议上游化 Mac)。 Mac v3.5.0 新增的 chartFreeContract 源码扫描测试用 path.relative(SRC_ROOT, fp) 收集所有声明 hook.chartFree = true 的组件相对路径,再与正斜杠期望表 toEqual 一一比对;Windows 上返回反斜杠分隔 → found 与期望表永不相等 → 契约测试假红。修:同 #29 的 .split(path.sep).join('/') 归一(macOS path.sep='/' 时为 no-op,行为逐字节不变)。本行原本缺失 —— 补丁/apply.sh/哨兵三层都在,唯独台账漏登记(PERF-R9 G8)。 |
patches/src__utils__chartFreeContract.pathsep.test.js.patch(marker horosa_win_pathsep_posix_v1;apply.sh §25) |
| 31 | astrostudyui/src/utils/perfMark.js + src/pages/index.js(累积)+ src/models/astro.js(累积)+ src/utils/requestDedupe.js + src/utils/__tests__/requestDedupe.test.js |
PERF-R9 前端:交互跨度观测 + L1 真 LRU(纯观测 / 纯修 bug,功能零降级;跨平台,建议上游化 Mac)。 ① 观测:改之前这套埋点量不出要验收的那个数 —— ':refresh-start' 全仓只有 changeTab 打点,切时间/改选项一次都不打,于是 markChartRefreshEnd 拿上一次切页签留下的陈旧 start 配 measure,量出秒级乃至分钟级垃圾;快车道(八字/紫微/数算)在 models/astro.js 提前 return,从不打 refresh-end,而 chartObj.chartId 变了 → render-complete 照样触发 → 三族一直报伪造的渲染时间;utils/perfMark.js 整个文件零调用点,window.__horosaPerf.summary() 永远返回 {}。修:perfMark 新增 markInteractionStart/markPanelReady(双 rAF 逼近「本帧已绘」,generation 去重),起点打在 changeCond 的防抖之前(否则量到的是防抖之后而非用户点下),快车道补 markChartRefreshEnd()。★ 起点 mark 名必须含 ':refresh-start' —— markChartRefreshEnd 按该子串找最近一个 start,改名即静默断链。② L1 真 LRU:requestDedupe 的 L1 命中后不重插,而 prune() 按 Map 插入序从头淘汰 ⇒ 它一直是 FIFO 而不是 LRU(原注释「Map 插入序 = 简易 LRU」正是误解源头;L2 的 warm 分支从一开始就对,只有 L1 漏了)。后果:一串后台预取会把用户正在反复访问的那条挤出去、预取自己反而活着 —— 预取覆盖面从 1 个端点扩到十几个技法之后,这个方向是反的,会主动伤害命中率。★ 刻意不刷新 ent.at:LRU 管淘汰顺序、TTL 管新鲜度,刷 at 会让热条目永不过期 = 偷改缓存语义(与 L2 做法一致)。配套回归断言已受控验证:撤掉那两行则该断言变红(1 failed / 12 passed),装回则 13/13 全绿。 |
patches/src__utils__perfMark.interactionSpan.js.patch(marker horosa_interaction_span_v1)+ patches/src__utils__requestDedupe.l1Lru.js.patch + patches/src__utils____tests____requestDedupe.l1Lru.test.js.patch(marker horosa_dedupe_l1_lru_v1);pages/index.js 与 models/astro.js 并入既有累积补丁并按 gotcha #48 把 guard marker 换成最新(markInteractionStart / horosa_interaction_span_v1);apply.sh §5 / §22 / §26 |
| 32 | astropy/astrostudy/perchart.py(累积)+ astropy/astrostudy/india/primitives.py + astropy/astrostudy/india/yoga_engine.py |
PERF-R9 输出确定性 —— set 顺序不得泄漏进响应(真 bug 修复;跨平台,强烈建议上游化 Mac)。 症状:同一张盘,每次重启软件看到的若干列表顺序都可能不同(内容相同、排列乱跳)。根因:CPython 默认开启哈希随机化(发货 app 不设 PYTHONHASHSEED),而这些结果把 set / 对 set 的推导直接吐进响应 —— 迭代顺序 = 哈希顺序 = 每进程不同。四处泄漏:① perchart.getParallel() 的赤纬平行/反平行(西洋盘)→ 改按 const.LIST_ALL_POINTS 行星序;② india/primitives.rasi_drishti 的 MOVABLE/FIXED/DUAL 是 set → 改按 SIGNS 黄道十二宫序;③ india/yoga_engine 三处 NATURAL_BENEFICS/MALEFICS 推导 → 改按 CLASSICAL_PLANETS 序(该文件 963 行原本就是这个正确写法,只是这三处没用);④ affliction_modifiers 同上 → 按 YOGA_PLANETS(含罗睺/计都)。成员逐元素不变、条数不变(⇒ base_score 不变),只把「任意且不稳定」变成「确定且符合专业次序」。实测(windows-adaptations/golden 跨进程比对,205 个星盘用例):修前 72 例仅顺序有别 → 修后 0 例;pytest 1133 passed(唯一红=既有的 alcabitius 字节钉)。★ 这同时是后续一切逐字节回归比对的前提 —— 不修它,星盘族的黄金永远不可能稳定。★ 附带记录:黄金台架起服务用 python -E,而 -E 会忽略所有 PYTHON* 环境变量,所以在那里钉 PYTHONHASHSEED 是无效的 —— 确定性必须来自产品代码本身(这也正是发货 app 的真实处境)。 |
patches/astropy__india__primitives.stableOrder.py.patch(marker horosa_rasi_drishti_stable_order_v1)+ patches/astropy__india__yoga_engine.stableOrder.py.patch(marker horosa_yoga_planet_order_v1);perchart.py 并入既有 astropy__perchart.chartMemo.py.patch 并按 gotcha #48 把 guard marker 换成最新的 horosa_decl_parallel_stable_order_v1;apply.sh §17 / §27 |
| 33 | vendor/kinqimen/jieqi.py + vendor/kinqimen/config.py + vendor/kinqimen/kinqimen.py |
PERF-R9 奇门引擎热路径 Tier-1(纯去冗余,零缓存语义;跨平台,建议上游化 Mac)。 实测 /qimen/pan 中位 282ms → 65.7ms(4.3×);全 3528 例黄金矩阵墙钟 835.5s → 142.4s(5.87×);逐字节零漂移,kill-switch 关闭后同样零漂移。四项:① horosa_qimen_jiazi_const_v1 —— 六十甲子是编译期常量却每请求重建 4,712 次(282,720 次 lambda + 286,319 次 str.format,占 profile 46%);20 个调用点已逐一审计为只读(切片 / new_list 的 slice+concat / dict(zip) / split_list / repeat_list,无一处就地变异)。② horosa_qimen_cse_v1 —— 四处同参函数调两遍:zhishi_pai/zhifu_pai 各两次(而 zhifu_n_zhishi 每请求被调 15 次、每次 ~20ms)、jq 两次、pan_earth/pan_earth_minute 各两次;改取自同一 dict 后 keys()/values() 一一对应的保证比原式更强(原式是两个独立构造的 dict,只是恰好相同)。③ horosa_qimen_lazyju_v1 —— 七处 {1:chaibu(...),2:zhirun(...),3:maoshan(...),4:wurun(...)}.get(option) 把四种定局法全部求值再丢弃三个,而其中三种都要走 sxtwl 逐日游标(_anchor_solstice 每请求 168 次、Day_hasJieQi 32,166 次);收敛为 config._select_ju。★★ 刻意不给 .get 默认值 —— config 侧原本无默认,option 越界时 qmju=None → 下一行 qmju[0] 抛 TypeError → 服务返回 ResultCode -1;kinqimen.py 的 .get(option, chaibu) 带默认写法不可照抄到 config,那会把越界请求从错误信封悄悄变成「按拆补法出盘」= 功能改变。黄金矩阵已钉住 option ∈ {0,5} 的 -1 信封。④ horosa_qimen_pan_memo_v1 —— Qimen 实例级 memo,消掉 overall() 对 pan() 的重复求值(原始耗时的 45%)。安全性地基(三条均已核实):webqimensrv:217 每请求新建实例;两个 thread-local 开关(日界/晚子时)在 :199-207 即构造之前设定 ⇒ 实例生命周期 == 请求生命周期,无跨请求/跨开关串染面;_json_safe(:47-61)重建所有 dict/list ⇒ 共享对象不泄漏进响应,:140 的 minute is not selected 身份判定值不变。★ config.pan_sky_minute 绝不可 memo —— kinqimen.gong_chengsun_minute 会 del sky["中"] 就地变异它的返回值(已核实:那是全文件唯一一处就地变异,且它来自 config 模块函数而非 Qimen 方法,故实例 memo 安全)。kill-switch:HOROSA_QIMEN_PAN_MEMO=0。 |
patches/vendor__kinqimen__jieqi.qimenPerf.py.patch + patches/vendor__kinqimen__config.qimenPerf.py.patch + patches/vendor__kinqimen__kinqimen.panMemo.py.patch(markers 同上四个;apply.sh §28)。哨兵并入 kinqimen 三文件既有的 SENT 条目(它们本就有正确性 needle,绝不另开同名键 —— gotcha #29) |
| 34 | flatlib-ctrad2/flatlib/ephem/swe.py |
PERF-R9 星历路径短路 —— 全局共享层,本轮单点收益最大(跨平台,强烈建议上游化 Mac)。 applySiderealMode 在每次 swe 调用时都走一趟 ensureEphePath,而后者无条件重设星历路径 —— 那是个幂等操作。cProfile 实测:单次 BirthJieQi.compute() 里 swisseph.set_ephe_path 被调 680 次、耗时 82 ms = 该端点的 61%。它所在的因果链贯穿三层:set_ephe_path → /jieqi/birth(154 ms,ChartController.baziAssemble 的最大单项)→ baziAssemble(~370 ms,冷 /chart 的 75%)→ 冷 /chart。安全性依据:swisseph.set_ephe_path 自 swe.py 的 swisseph.set_ephe_path = _guardedSetEphePath 起就是进程内唯一入口(已 grep 全仓确认没有任何 from swisseph import set_ephe_path 绕过它;vendor/kinastro/** 的 swe.set_ephe_path("") 全部经运行时属性查找路由到这里)⇒ 它可以可靠持有「当前真正生效的路径」;一旦有外部调用者改动,比较立刻失配、照旧恢复,语义零变化。实测:BirthJieQi.compute 135 → 12.5 ms(10×);/jieqi/birth 端到端 154 → 28.5 ms(5.4×);全 3823 例黄金矩阵零漂移(唯 6 例 kentang.wangji.* 属另一项 owner 批准的载荷变更,已单独归因)。kill-switch:HOROSA_EPHE_PATH_FASTPATH=0。 |
patches/flatlib__ephem.swe.ephePathFastpath.py.patch(marker horosa_ephe_path_fastpath_v1;apply.sh §29)。注意与 ephem.py 的 starLru 是不同文件,两者互不影响 |
| 35 | 41 个前端组件(稳定 React key)+ 13 个技法结果缓存接入点 + 皇极经世/玄学史载荷按需 + astropy/tests/test_india_ephemeris_degrade.py | PERF-R9 前端渲染 + 技法结果缓存 + 超大载荷按需取(功能零降级;跨平台,建议上游化 Mac)。 ① 稳定 React key(horosa_stable_react_keys_v1):仓库里曾有 222 处 key={randomStr(8)}(41 文件)——随机 key 每次渲染都变 ⇒ React 无法 diff ⇒ 整棵子树卸载重建而非打补丁。全部换为内容派生、兄弟间唯一的稳定 key。★ 陷阱:看似单例的块可能位于外层 for/map 内,裸字面量会跨迭代撞键(AstroInfo 的 MinDelta/MarsSaturn/SunMoon/VenusJupiter 正是如此,改用 ${key}-minDelta 复合键)。验证:umi 全绿、0 个 React key 警告;另对 33 个无测试覆盖的组件做了 AST 静态查重。仓库级门 check_no_random_react_keys 永久禁止其回归。 ② kentang 结果缓存(horosa_kentang_result_cache_v1):14 个 raw-fetch 调用点里 11 个零缓存(绕过 utils/request ⇒ 绕过 requestDedupe/chartMem/全部 LRU),另 9 处 per-instance 缓存切页签即毁。统一走新增的 files/astrostudyui/src/services/_kentangResultCache.js(语义照搬 services/qizheng.js:66-98)。★★ 绝不缓存随机起卦:地占/荆诀本就排除;五兆是本轮查出来的 —— webwuzhaosrv 在 mode∈{day,hour,minute,tang} 且无 manual_splits 时走 random.randint,故加 wuzhaoCacheable() 门,只在 mode==='ganzhi' 或 manual===true 时缓存(太玄用 random 但由 _with_seed 按 payload.seed 确定性播种且起筮换 seed,安全)。全部门在 techniqueResultCacheEnabled() 上。 ③ 超大载荷按需(owner 批准):皇极经世 /wangji/pan 原本每次随响应发 979,910 字节典籍全文 → 实测 1,961,244 → 27,249 B(72×),新增 /wangji/classic 单取 + 前端按 classicKey 缓存一次;「换章节/换视图」仍是纯本地瞬时切换;AI 导出三条路径全覆盖。玄学史 microchronology 原本无 LIMIT 无分页吐全表 21 列(含三长文本列)且绕过 load_events 的 memo,实测第二次 1,127.7ms → 11.7ms(96×)、载荷 26.4MB → 257KB,计数/筛选语义逐字节等价;预热同时改瞄真正贵的 microchronology。 ④ 星历降级测试前置条件修正:ensureEphePath 短路后星历文件句柄常驻,Windows 不允许改名被打开的文件,故测试先调新增的 swe.closeEphemerisFiles() 再 rename —— 断言一字未动。 | ① 41 个补丁:patches/src__components__astro__AstroAspect.perfR9.js.patch · patches/src__components__astro__AstroFirdaria.perfR9.js.patch · patches/src__components__astro__AstroGivenYear.perfR9.js.patch · patches/src__components__astro__AstroInfo.perfR9.js.patch · patches/src__components__astro__AstroLunarReturn.perfR9.js.patch · patches/src__components__astro__AstroProfection.perfR9.js.patch · patches/src__components__astro__AstroSolarArc.perfR9.js.patch · patches/src__components__astro__AstroSolarReturn.perfR9.js.patch · patches/src__components__astro__AstroYearSystem129.perfR9.js.patch · patches/src__components__cntradition__GanHeCong.perfR9.js.patch · patches/src__components__cntradition__Gods.perfR9.js.patch · patches/src__components__cntradition__MDSYear.perfR9.js.patch · patches/src__components__cntradition__MainDirection.perfR9.js.patch · patches/src__components__cntradition__MainDirectionSimple.perfR9.js.patch · patches/src__components__cntradition__PaiBaZi.perfR9.js.patch · patches/src__components__cntradition__SmallDirection.perfR9.js.patch · patches/src__components__cntradition__Zhu.perfR9.js.patch · patches/src__components__cntradition__ZhuMing12.perfR9.js.patch · patches/src__components__cntradition__ZiHeCong.perfR9.js.patch · patches/src__components__commtools__BaziPattern.perfR9.js.patch · patches/src__components__commtools__BaziPithy.perfR9.js.patch · patches/src__components__commtools__Calculator.perfR9.js.patch · patches/src__components__commtools__CuanGong12Desc.perfR9.js.patch · patches/src__components__commtools__CuanGong12Query.perfR9.js.patch · patches/src__components__commtools__InverseBazi.perfR9.js.patch · patches/src__components__commtools__NaYing.perfR9.js.patch · patches/src__components__comp__EditableTags.perfR9.js.patch · patches/src__components__comp__TipsBoard.perfR9.js.patch · patches/src__components__germany__AspectToMidpoint.perfR9.js.patch · patches/src__components__gua__GuaSym.perfR9.js.patch · patches/src__components__gua__MeiyiGuaSym.perfR9.js.patch · patches/src__components__guazhan__GuaDesc.perfR9.js.patch · patches/src__components__relative__AntisciaInfo.perfR9.js.patch · patches/src__components__relative__AspectInfo.perfR9.js.patch · patches/src__components__relative__MidpointInfo.perfR9.js.patch · patches/src__components__ruleziwei__RuleHouses.perfR9.js.patch · patches/src__components__ruleziwei__RuleHuaDesc.perfR9.js.patch · patches/src__components__ruleziwei__RuleSihua.perfR9.js.patch · patches/src__components__ruleziwei__RuleStars.perfR9.js.patch · patches/src__components__user__CaseList.perfR9.js.patch · patches/src__components__user__ChartList.perfR9.js.patch。 ② 13 个补丁:patches/src__components__calendar__HuangLiMain.perfR9.js.patch · patches/src__components__dunjia__DunJiaCalc.perfR9.js.patch · patches/src__components__geomancy__GeomancyMain.perfR9.js.patch · patches/src__components__jingjue__JingJueMain.perfR9.js.patch · patches/src__components__jinkou__JinKouCalc.perfR9.js.patch · patches/src__components__jinkou__JinKouMain.perfR9.js.patch · patches/src__components__kinastro__KinAstroMain.perfR9.js.patch · patches/src__components__lrzhan__LiuRengMain.perfR9.js.patch · patches/src__components__shenyishu__ShenYiShuMain.perfR9.js.patch · patches/src__components__taixuan__TaiXuanMain.perfR9.js.patch · patches/src__components__taiyi__TaiYiCalc.perfR9.js.patch · patches/src__components__wuzhao__WuZhaoMain.perfR9.js.patch · patches/src__services__xuanshi.perfR9.js.patch(外加全量拷贝层新增 files/astrostudyui/src/services/_kentangResultCache.js)。★v3.5.1 收敛注:GeomancyMain.perfR9 补丁与 _kentangResultCache.js(含 files/ 双份)已退役 —— 地占页上游改版自带守卫,kentang 结果缓存被上游 fetch 级 kentangCache 取代(见行 42/43)。 ③④ 其余:patches/astropy__astrostudy__xuanshi__celestial.perfR9.py.patch · patches/astropy__tests__test_india_ephemeris_degrade.perfR9.py.patch · patches/astropy__websrv__webwangjisrv.perfR9.py.patch · patches/astropy__websrv__webxuanshisrv.perfR9.py.patch · patches/src__components__huangji__HuangJiMain.perfR9.js.patch · patches/src__components__xuanshi__XuanShiMicro.perfR9.js.patch。apply.sh §30 |
| 36 | astrostudyui/src/utils/stepPrefetch.js + utils/request.js + utils/chartFetch.js + utils/techniqueChartFree.js + utils/__tests__/stepPrefetch.test.js + 5 个技法主组件(astro/IndiaChartMain.js · auxchart/AuxChartMain.js · dunjia/DunJiaMain.js · sanshi/SanShiUnitedMain.js · taiyi/TaiYiMain.js)+ 3 个 chartFree 声明页(fengshui/FengShuiMain.js · calendar/CalendarMain.js · cntradition/CnTraditionMain.js)+ 全量拷贝层 3 个新文件(utils/dataWarmTasks.js · utils/__tests__/dataWarmTasks.test.js · utils/__tests__/stepPrefetchWhitelist.test.js)+ 既有累积补丁 12 个目标(见「如何恢复」列) | PERF-R9 Ship 7 预取与预热全覆盖(纯前端、全部带 kill-switch、功能零降级;跨平台,建议上游化 Mac)。 ① 白名单从注释变运行时闸(horosa_prefetch_runtime_whitelist_v1):旧任务契约是 {name, run},run 是不可内省的闭包 —— PREFETCH_ALLOWED_PATHS/PREFETCH_FORBIDDEN_MARKERS 只是注释加一个 jest 快照,submitStepPrefetch 从不看 URL;而旧允许集里裸 /pan 一条匹配不到任何真实路径(真路径是 /qimen/pan、/taiyi/pan…)= 形同虚设。于是任何登记方都能把【随机起卦 / 取现时 / 流式】端点塞进预取队列 —— 预取它们等于把随机结果或「此刻」钉死进缓存 = 功能性降级(比慢更糟)。修法两层:①契约加 path 声明位,提交期不合格即丢弃(不抛错:预取是优化不是功能,丢一条只是回到冷即付);②纵深防御(path 是自述的,可能与 run 里真发的 URL 不符):pump 期间置 ambient 标志,request.js / chartFetch.js 对不合格 URL 直接拒发并计数。★ kentang 全族走 chartFetch 的裸 fetch(不经 utils/request),没有第二层整族在任何白名单之外。★ kentang 的 /{key}/pan 逐条枚举、绝不通配 —— /*/pan 会把地占(random seed)与五兆(random.randint)一并放进来。★ 非预取作用域两闸恒放行 ⇒ 用户真实请求逐字节零行为变化。 ② 技法步进预取注册表(horosa_prefetch_registry_v1):此前只有 /chart 一个端点进预取,非占星页(印占 / 辅盘 / 遁甲 / 三式 / 太乙 / 紫微 / 七政 / 六壬 / 星运)gate 面板的是技法端点,点下一步照样等一次冷计算。各技法在自己的组件里登记 registerStepPrefetcher —— 登记必须在组件内:构参吃组件态(流派 / 子页 / 引擎模式),模块级构不出与真点逐字节同键的 body。两段式技法(遁甲 / 太乙 / 三式)只登记 stage-1(确定性历法计算)。配套:任务序改近端优先 + 技法端点先于同向 chart(旧序把 chart±1 排最前,技法任务恒被预算砍掉)、技法登记方收到的改为已步进的 fields(旧版传基准 fields = 预取当前那张盘 = 白打)、预算 3→5、间隔改自适应 max(80ms, 上个任务耗时)、chartMem 容量 96→192(否则连点步进会把用户刚看过的盘挤出去)。 ③ chartFree 快车道扩容(horosa_chart_free_declared_v1):声明 hook.chartFree = true 的页,fetchByFields 走快车道 —— fields 立即提交、不等 /chart 网络(整整省掉一次往返)。上一轮遗留的三个「已核实但无从声明」候选本轮核毕迁入:风水页此前连 hook 都没接(故无从声明),本轮补接 hook 只为承载这条声明,不注册 .fun。★ 组件里的声明与 utils/techniqueChartFree.js 的登记是一对:只登记不声明 = 无效;只声明不登记 = chartFreeContract 契约测试红。 ④ 数据层预热注册表(horosa_data_warm_registry_v1):预热清单原本写死在 pages/index.js 的一条 4 元素数组里 —— 与技法零关系的页面组件持有技法知识,漏项没人发现(紫微 /ziwei/birth 首点概率最高,却整轮不在组里)。改注册表后追加一条 = 一行登记,Map 插入序 = 首点概率序 = 执行序;本轮据此补入紫微 / 遁甲 stage-1 / 太乙 stage-1 / 分至 /jieqi/year 四条(分至是本组唯一的重端点,排最后一位,只吃真正的空闲尾巴)。★ 预热/预取一律显式 retry:{retries:0} —— 后端重启窗口里 N 个深度预取绝不能变成 N×10 次退避重试风暴。 ⑤ 就地变异根治(horosa_change_cond_no_mutate_v1,pages/index.js):changeCond 的 {...fields} 只拷顶层 ⇒ flds.date 与 fields.date 是同一个对象,flds.date.value = x 改的是 state 里那个对象本身 —— 任何按引用比较的 React.memo / shouldComponentUpdate 都会判错。这是渲染优化的前提,不修它后面加多少 memo 都是白加。 | 新增补丁 13 个:patches/src__utils__stepPrefetch.prefetchWhitelist.js.patch · patches/src__utils__request.prefetchWhitelist.js.patch · patches/src__utils__chartFetch.prefetchWhitelist.js.patch(markers horosa_prefetch_runtime_whitelist_v1)· patches/src__components__astro__IndiaChartMain.prefetchRegistry.js.patch · patches/src__components__auxchart__AuxChartMain.prefetchRegistry.js.patch · patches/src__components__dunjia__DunJiaMain.prefetchRegistry.js.patch · patches/src__components__sanshi__SanShiUnitedMain.prefetchRegistry.js.patch · patches/src__components__taiyi__TaiYiMain.prefetchRegistry.js.patch · patches/src__utils____tests____stepPrefetch.prefetchRegistry.test.js.patch(markers horosa_prefetch_registry_v1)· patches/src__utils__techniqueChartFree.chartFree.js.patch · patches/src__components__fengshui__FengShuiMain.chartFree.js.patch · patches/src__components__calendar__CalendarMain.chartFree.js.patch · patches/src__components__cntradition__CnTraditionMain.chartFree.js.patch(markers horosa_chart_free_declared_v1)。 全量拷贝层新增 3 个(Mac 基线里不存在,regen_patch.py 无从 diff):files/astrostudyui/src/utils/dataWarmTasks.js · files/astrostudyui/src/utils/__tests__/dataWarmTasks.test.js · files/astrostudyui/src/utils/__tests__/stepPrefetchWhitelist.test.js。 既有累积补丁按 gotcha #48 重新 regen(Ship 7 的改动并入,不新开第二个补丁),guard 串换成最新 marker:patches/src__pages__index.js.patch(→horosa_change_cond_no_mutate_v1)· patches/src__utils__idleWarmQueue.dataWarmGroup.js.patch(→horosa_data_warm_registry_v1)· patches/src__components__ziwei__ZiWeiMain.js.patch · patches/src__components__guolao__GuoLaoChartMain.warmNatal.js.patch · patches/src__components__direction__AstroDirectMain.warmPd.js.patch · patches/src__components__lrzhan__LiuRengMain.perfR9.js.patch(以上四条→horosa_prefetch_registry_v1)· patches/src__components__jieqi__JieQiChartsMain.neighborPrefetch.js.patch(→warmJieqiYear)· patches/src__models__astro.precomputeFetch.js.patch · patches/src__services__astro.chartMemValidOnly.js.patch · patches/src__components__germany__AstroMidpoint.warmMidpoint.js.patch(guard 不变,只并入新钉)。apply.sh §31 |
| 37 | astropy/websrv/webchartsrv.py + astropy/websrv/kentang/registry.py + astrostudyui/src/utils/perfMark.js | PERF-R9 收尾三项(两项性能 + 一项验收可测性;跨平台,建议上游化 Mac)。 ① 请求路径上的 stdout 转储(horosa_chart_no_stdout_dump_v1):/chart 每次都 print(data) —— 打印的是整个请求字典,含出生日期 / 时间 / 经纬度 / 地名。两重代价:打包件里 stdout 经管道回主进程并落日志文件,写盘同步发生在请求路径上;而把用户出生信息持续写进日志文件本身就不应该。它是调试残留,产线无人读。改为默认关、需要时以 HOROSA_CHART_DEBUG_DUMP=1 显式开。 ② 请求路径内惰性 import 的重模块预装(horosa_kentang_prewarm_modules_v1):kintaiyi.pan(enable_game_theory=True) 才 from .game_theory import TaiyiGame,而 game_theory 是 793 行的重模块 —— 实测冷导入 528.1 ms、温(sys.modules 命中)0.001 ms。用户在太乙里第一次勾「博弈论」白等的就是这半秒,与算法无关。★ 刻意拆成独立函数 prewarm_kentang_modules() 并放在 STARTUP_GATE.set() 之后,绝不并进 prewarm_kentang_services():后者跑在门之前,并进去等于把启动门整整推迟 528 ms(启动预算是硬线)。同文件的 xuanshi_summary_warmup_v1 立的就是这条规矩 —— 预热只许吃空闲,不许延长任何人的等待窗。只 import 不调用 ⇒ 零盘面副作用,任何请求输出逐字节不变;吞错 ⇒ 失败时真实请求仍走原来的 import 并按原样报错(口径不分叉)。kill-switch:HOROSA_KENTANG_MODULE_PREWARM=0。 ③ 验收可测性(horosa_perf_reset_v1):owner 的验收标准是「点击 → 中栏+右栏画完 ≤ 1 秒」,而切到某技法页签会先付一次性装载成本,那不属于「单次设置/时间调整」的预算。暴露 perfReset() 供验收台架在「切页签并稳定之后」清零,使随后 N 次步进的 p95 只反映稳态单次操作。业务代码不调它。 | patches/astropy__webchartsrv.xuanshiWarmup.py.patch(累积补丁并入,guard 换最新 marker horosa_kentang_prewarm_modules_v1)· patches/astropy__kentang_registry.modulePrewarm.py.patch(新增)· patches/src__utils__perfMark.interactionSpan.js.patch(累积补丁并入,guard 换 horosa_perf_reset_v1)。驱动脚本 desktop_installer_bundle/scripts/perf_acceptance.cjs 与调试端口开关 electron/main.js:horosa_perf_remote_debug_v1 属壳层,不在本表(壳层不随 Mac 同步) |
| 38 | 73 个前端目标(占星族 30 · 其余技法壳 20 · 汉堡学派 5 · 八字族 5 · 黄历族 4 · 紫微族 4 · 数算族 4 · comp/FreezeInactive.js 1)+ 既有累积补丁 20 个目标的 guard 更新(见「如何恢复」列) | PERF-R9 Ship 6 前端渲染优化(纯前端、全部带 kill-switch、功能零降级;跨平台,建议上游化 Mac)。 三条主线互相咬合 —— 没有 ① 就量不出验收的那个数,没有 ③ 就永远在为看不见的子页签付渲染,没有 ② 则父组件的一次无关 setState 照样穿透到最重的那棵子树。 ① 面板就绪观测终点(horosa_panel_ready_v1,101 处 markPanelReady):改之前 render-complete 只由 chartObj.chartId 变化触发(= 排盘回来那一刻),而右栏技法面板自己那次 setState 之后的重绘完全不在计内 ⇒ owner 的验收口径「点击 → 中栏 + 右栏画完 ≤ 1 秒」根本量不出来(量到的是排盘返回时刻,面板还没画)。修:在每个技法「结果落定的那一次 setState 的回调」里打 markPanelReady(双 rAF 逼近「本帧已绘」+ generation 去重,同一轮只记一次)。纯观测,零行为变化;总闸 horosa.perf.interactionMarks。 ② sCU / React.memo 拆分:盘面与重表格组件补 shouldComponentUpdate,一律走 utils/chartUpdateGuard 的 wrapperPropsEqual / shallowPropsEqual —— 函数型 props 视为恒等、显示数组按内容比、自身 state 引用变恒重渲(这一条是安全性地基:任何 didUpdate 里的重画判据都不会被吞掉,因为 props/state 一变必渲)。本轮只吃掉「props 与 state 都没变」的那种纯冗余重渲 —— 那种情况下旧代码走完 render 后各自的 componentDidUpdate 比较也全等、什么都不做,即跳过前后行为逐字节一致,只少付一次 reconcile。各族自带 marker:horosa_panel_scu_v1(黄历)· horosa_shusuan_native_scu_v1(数算/一掌经)· horosa_ziwei_chart_scu_v1 / horosa_ziwei_input_scu_v1 / horosa_ziwei_luck_scu_v1 / horosa_ziwei_pattern_scu_v1(紫微四块)· horosa_bazi_finechart_scu_v1 / horosa_bazi_info_split_v1 / horosa_bazi_chartbazi_memo_v1 / horosa_bazi_child_memo_v1 / horosa_bazi_param_memo_v1 / horosa_bazi_flow_derive_memo_v1 / horosa_bazi_deadwork_v1(八字族)· horosa_dial_scu_v1 / horosa_frames_scu_v1 / horosa_lazy_right_panels_v1(汉堡学派)· horosa_guolao_doc_scu_v1 / horosa_guolao_doc_static_rows_v1(七政星宗文档)· horosa_kinastro_center_memo_v1 / horosa_kinastro_render_memo_v1(印占)· horosa_shallow_scu_v1(3D 主限球)· horosa_markdown_lru_v1(AI 流式 markdown→HTML 渲染 LRU,避免每 chunk 全量重渲)· horosa_aspect_dom_memo_v1 / horosa_no_mutate_chart_params_v1 / horosa_no_state_mutation_v1(相位 DOM 记忆与两处就地变异根治)。kill-switch:horosa.perf.chartSCU=0 ⇒ 比较器恒返 false = 逐字节旧行为。★ 前提是 Ship 7 已根治 pages/index.js 的 changeCond 就地变异(见 #36 / FE-18) —— 不修它,「旧 fields」与「新 fields」的嵌套引用完全相同,任何按引用比较的 memo/sCU 都会判错,加多少 memo 都是白加。 ③ 子页签冻结(horosa_freeze_subtabs_v1,comp/FreezeInactive.js 的 FreezeSubTab):antd Tabs 默认把全部子页签常驻渲染 —— 改一次时间或选项,就把每一页重画一遍(推运页是「一个方法一张盘 + 一套表」× N 个方法)。改受控 activeKey + FreezeSubTab:只渲前台那一个,切回时拿本轮最新 children 立即渲一帧 —— 不卸载、不重发请求、不丢滚动位置。配套 horosa_controlled_tab_clamp_v1:页签集合由后端结果决定、会随结果变化,用户选过的键仍在就保持,否则回落默认键、再不在就取首个,绝不停在不存在的键上显示空白。kill-switch:horosa.perf.freezeSubTabs=0(恒渲旧行为)/ horosa.perf.subTabDeferMount=0(不延迟首次挂载)。 ④ 顺带的两处纯正确性修复:calendar/YearAuspiciousPanel.js 的 catOptions 只随「含丧葬」开关变化,却每次父渲都重建数组 → 提进 useMemo,并给 antd List 补 rowKey(否则退回下标键);astro/__tests__/chartSCU.test.js —— MidpointMain 因接入 FreezeSubTab 首次有了 state,sCU 随之加 nextState !== this.state 守卫,而旧测试用单参调用 sCU ⇒ nextState === undefined ⇒ 与 this.state 必然不等 ⇒ 恒返 true:那一批期望 true 的用例因为错误的原因而通过,不再检验 props 比较 = 一批假绿。改为统一经 scu(c, next) 传 c.state(真实 React 调 sCU 时永远会传 nextState),并补一条「state 变 → true」的用例。★ 绝不反过来放宽组件的 state 守卫去迁就单参调用 —— 那是拿产品代码迁就测试写法。 | 新增补丁 73 个。 ① markPanelReady 观测终点(guard horosa_panel_ready_v1,28 个):patches/src__components__acg__AstroAcg.perfR9.js.patch · patches/src__components__astro__AstroAgePoint.perfR9.js.patch · patches/src__components__astro__AstroBalbillus.perfR9.js.patch · patches/src__components__astro__AstroChartMain.perfR9.js.patch · patches/src__components__astro__AstroDecennials.perfR9.js.patch · patches/src__components__astro__AstroDistributions.perfR9.js.patch · patches/src__components__astro__AstroExtraReturns.perfR9.js.patch · patches/src__components__astro__AstroKeypoints.perfR9.js.patch · patches/src__components__astro__AstroLunationPhase.perfR9.js.patch · patches/src__components__astro__AstroPersianDirected.perfR9.js.patch · patches/src__components__astro__AstroPlanetaryAges.perfR9.js.patch · patches/src__components__astro__AstroPlanetaryArc.perfR9.js.patch · patches/src__components__astro__AstroPrenatalSyzygy.perfR9.js.patch · patches/src__components__astro__AstroPrimaryDirection.perfR9.js.patch · patches/src__components__astro__AstroPrimaryDirectionChart.perfR9.js.patch · patches/src__components__astro__AstroReturnTimeline.perfR9.js.patch · patches/src__components__astro__AstroTriplicityRulers.perfR9.js.patch · patches/src__components__astro__AstroZR.perfR9.js.patch · patches/src__components__auxchart__AstroDraconicLab.perfR9.js.patch · patches/src__components__auxchart__AstroHarmonicLab.perfR9.js.patch · patches/src__components__cntradition__BaZi.perfR9.js.patch · patches/src__components__cntradition__BaZiLuckFlowPanel.perfR9.js.patch · patches/src__components__election__ElectionMain.perfR9.js.patch · patches/src__components__germany__UranianGraphicEphemeris.perfR9.js.patch · patches/src__components__germany__UranianHouseFrames.perfR9.js.patch · patches/src__components__guice__GuiceMain.perfR9.js.patch · patches/src__components__hellenastro__AstroChart13.perfR9.js.patch · patches/src__components__horary__HoraryMain.perfR9.js.patch。 ② FreezeSubTab 子页签冻结(guard horosa_freeze_subtabs_v1,27 个):patches/src__components__aianalysis__AIAnalysisMain.perfR9.js.patch · patches/src__components__astro3d__AstroChartMain3D.perfR9.js.patch · patches/src__components__astro__AstroDoubleChartMain.perfR9.js.patch · patches/src__components__astro__AstroEphemeris.perfR9.js.patch · patches/src__components__astro__AstroJaynesProgressions.perfR9.js.patch · patches/src__components__astro__AstroProgressions.perfR9.js.patch · patches/src__components__astro__AstroRelative.perfR9.js.patch · patches/src__components__astro__AstroVedicProgressions.perfR9.js.patch · patches/src__components__auxchart__AstroRelocationLab.perfR9.js.patch · patches/src__components__cntradition__BaZiLegacyView.perfR9.js.patch · patches/src__components__cnyibu__CnYiBuMain.perfR9.js.patch · patches/src__components__commtools__CommToolsMain.perfR9.js.patch · patches/src__components__comp__FreezeInactive.perfR9.js.patch · patches/src__components__dice__DiceMain.perfR9.js.patch · patches/src__components__feigong__FeiGongMain.perfR9.js.patch · patches/src__components__germany__AstroGermany.perfR9.js.patch · patches/src__components__germany__MidpointMain.perfR9.js.patch · patches/src__components__germany__UranianDialMain.perfR9.js.patch · patches/src__components__guazhan__GuaZhanMain.perfR9.js.patch · patches/src__components__guolao__GuoLaoMoiraPanel.perfR9.js.patch · patches/src__components__mundane__MundaneMain.perfR9.js.patch · patches/src__components__shusuan__ZhengChuanMain.perfR9.js.patch · patches/src__components__suzhan__SuZhanMain.perfR9.js.patch · patches/src__components__tarot__TarotMain.perfR9.js.patch · patches/src__components__tongshefa__TongSheFaMain.perfR9.js.patch · patches/src__components__xiaochengtu__XiaoChengTuMain.perfR9.js.patch · patches/src__components__xiaoliuren__XiaoLiuRenMain.perfR9.js.patch。 ③ sCU / memo 各族(guard 取该文件内位置最靠后的 marker,14 个):patches/src__components__astro3d__AstroPDSphere.perfR9.js.patch · patches/src__components__calendar__NongLiMain.perfR9.js.patch · patches/src__components__calendar__RiziMain.perfR9.js.patch · patches/src__components__calendar__TongshuMain.perfR9.js.patch · patches/src__components__cntradition__BaZiAppInfoPanel.perfR9.js.patch · patches/src__components__cntradition__BaZiFineChart.perfR9.js.patch · patches/src__components__guolao__GuoLaoStarSectDoc.perfR9.js.patch · patches/src__components__shusuan__CanPingMain.perfR9.js.patch · patches/src__components__shusuan__HeLuoMain.perfR9.js.patch · patches/src__components__yizhangjing__YiZhangJingMain.perfR9.js.patch · patches/src__components__ziwei__ZWLuckPanel.perfR9.js.patch · patches/src__components__ziwei__ZWPatternPanel.perfR9.js.patch · patches/src__components__ziwei__ZiWeiChart.perfR9.js.patch · patches/src__components__ziwei__ZiWeiInput.perfR9.js.patch。 ④ 本轮改动不带 horosa_* marker 的 4 个(两个纯 sCU 壳 / 一个 useMemo+rowKey / 一个测试)—— guard 取「补丁引入且改前不存在」的代码串(wrapperPropsEqual × 2 · rowKey= · c.shouldComponentUpdate(nextProps,),并原样钉进各自的哨兵条目(R4 要求 guard == 钉):patches/src__components__astro____tests____chartSCU.test.perfR9.js.patch · patches/src__components__calendar__YearAuspiciousPanel.perfR9.js.patch · patches/src__components__lrzhan__LiuRengChart.perfR9.js.patch · patches/src__components__mingother__MingOtherMain.perfR9.js.patch。 以上全部接在 apply.sh §32(按目录分 8 组:comp / 占星族 / 黄历族 / 八字族 / 紫微族 / 数算族 / 汉堡学派 / 其余技法壳)。 既有累积补丁 20 个:只改 guard 串、绝不新开第二行(gotcha #48 —— 累积补丁的 guard 取文件内最新 marker,这样部分应用也能被检测到;R1 双射也不允许同一补丁出现两行):patches/src__components__astro__AstroFirdaria.perfR9.js.patch · patches/src__components__astro__AstroGivenYear.perfR9.js.patch · patches/src__components__astro__AstroLunarReturn.perfR9.js.patch · patches/src__components__astro__AstroProfection.perfR9.js.patch · patches/src__components__astro__AstroSolarArc.perfR9.js.patch · patches/src__components__astro__AstroSolarReturn.perfR9.js.patch · patches/src__components__astro__AstroYearSystem129.perfR9.js.patch · patches/src__components__calendar__HuangLiMain.perfR9.js.patch · patches/src__components__cntradition__PaiBaZi.perfR9.js.patch · patches/src__components__commtools__BaziPattern.perfR9.js.patch · patches/src__components__geomancy__GeomancyMain.perfR9.js.patch · patches/src__components__huangji__HuangJiMain.perfR9.js.patch · patches/src__components__jingjue__JingJueMain.perfR9.js.patch · patches/src__components__jinkou__JinKouMain.perfR9.js.patch · patches/src__components__kinastro__KinAstroMain.perfR9.js.patch · patches/src__components__lrzhan__LiuRengMain.perfR9.js.patch · patches/src__components__shenyishu__ShenYiShuMain.perfR9.js.patch · patches/src__components__taixuan__TaiXuanMain.perfR9.js.patch · patches/src__components__wuzhao__WuZhaoMain.perfR9.js.patch · patches/src__services__xuanshi.perfR9.js.patch(分布在 apply.sh §30a / §30b / §30c)。 哨兵:70 个目标新开 SENT 条目;acg/AstroAcg.js · aianalysis/AIAnalysisMain.js · mundane/MundaneMain.js 三个目标已有条目 → 本轮的钉并入既有 list,连同上述 20 个 guard 更新的目标,共 23 个条目走并入,一个同名键都没有新开(gotcha #29 / G1 / G7 已三次踩过)。 |
| 39 | astrostudyui/src/components/comp/DateTimeSelector.js + 全量拷贝层 3 个新文件(utils/stepPrefetchArm.js · utils/__tests__/stepPrefetchArm.test.js · utils/__tests__/perfMark.test.js)+ 既有累积补丁 14 个目标(models/astro.js·pages/index.js·utils/stepPrefetch.js·utils/perfFlags.js·utils/requestDedupe.js·ziwei/ZiWeiMain.js·dunjia/DunJiaMain.js·kinastro/KinAstroMain.js·astro3d/AstroChartMain3D.js·cnyibu/CnYiBuMain.js·huangji/HuangJiMain.js·taixuan/TaiXuanMain.js·shenyishu/ShenYiShuMain.js·utils/__tests__/stepPrefetch.test.js) | PERF-R10 Ship2「选步长即武装」预取(horosa_step_prefetch_arm_v1;纯前端、kill-switch、功能零降级;跨平台,建议上游化 Mac)。 病根三连:①预取单位只来自上一次步进 hint,无 hint 时硬编码 'm'(models/astro.js:450)——选完新步长的第一下必 miss(owner 原话「第一下卡之后不卡」);②DateTimeSelector.changeTimeType(全站唯一步长入口)只 setState 零预取;③submitStepPrefetch 全仓唯一调用点在 fetchByFields settle,而紫微步进走本地漏斗(onFieldsChange→astro/save+直调 requestZiWei)、遁甲未确认步进只落 localFields —— 两族登记的预取器从未被触发过。武装 = 四个时机把「当前档位 ±1..±depth」提前算好:(a)选步长(notifyStepUnitSelected,单点覆盖全部技法;sameMinute 语境闸滤 moira 流年/案例编辑旁路时间条);(b)settle 兜底(无 stepHint 也按「该技法最近档位」武装,不再硬编码 'm');(b′)本地漏斗(ZiWeiMain/DunJiaMain/KinAstroMain settle 自武装,skipChart);(c)切页签后 300ms(tabOverride 直给 key)。构造器经 registerArmPlanBuilder 注入(utils 不反向 import models),与真点共用同一 fieldsToParams 路径 ⇒ 键逐字节同;任务仍经 submitStepPrefetch ⇒ 白名单双闸/串行泵/latest-wins 零绕过;NO_ARM_TABS(随机/取现时/流式/浏览型)显式排除。计划形状:武装 [±1,±2,..±depth] 对称交错,有向 [+1,-1,+2..+depth];depth 缺省时旧路径逐字节不变;BUDGET_PER_SETTLE 5→12(串行泵即节流阀,后端池 30 vs 最坏 ~13 已核)。配套:dedupe 白名单补 /nongli/ /calendar/ /bazi/ /chart3d /common/(EXCLUDES 加 /common/time)——允许预取的 request() 端点必须可缓存,否则白取;P6 新登记:KinAstroMain(moduleKey 动态键+换轨迁移 _syncStepPrefetcher)、astrochart3D(非地心才产任务)、cnyibu(单键转发 huangji/taixuan/shenyishu 三确定性子页;皇极 /wangji/pan 顺带补上 horosa_kentang_result_cache_v1 直连缓存 —— 此前裸奔,预取无处落桶);金口两阶段暂缓(nongli 源不走共享桥,见 CONTRACT_EXEMPTIONS p6 行)。开关:horosa.perf.stepPrefetchArm(默认开)/ horosa.perf.stepPrefetchDepth(默认 3,0..5)。新 P5/P6 覆盖门:navigationPages 每键必有 markPanelReady 归属 / registerStepPrefetcher 登记(或 CONTRACT_EXEMPTIONS 显式豁免),负向自证见 SELFCHECK_LOG。 | 五层契约:新补丁 src__components__comp__DateTimeSelector.stepArm.js.patch(v3.5.1 已退役:触发线换血为上游 fireStepSelectPrefetch,武装引擎经 models/astro.js registerStepSelectHandler 接管) + apply.sh §33(cp×3+apply×1)+ 14 个累积补丁已 regen(R7 新鲜度门核);哨兵:horosa_step_prefetch_arm_v1(stepPrefetchArm.js/DateTimeSelector/astro.js 等)+ stepPrefetchArmEnabled/stepPrefetchDepth(perfFlags)+ P5/P6 门自身;台账:PERF_INVENTORY FE-23。 |
| 40 | 后端 Python 五连(PERF-R10 Ship3):vendor/kinqimen/{jieqi,config}.py + astropy/websrv/webqimensrv.py + astropy/websrv/kentang/kinastro_common.py + astropy/astrostudy/perchart.py + astropy/websrv/{webchartsrv,webpredictsrv}.py + kin 常量全族(vendor/kintaiyi/src/kintaiyi/{jieqi,config,kinliuren}.py·vendor/kinwuzhao/{jieqi,config}.py·vendor/shenyishu/shenyishu.py·vendor/kinjinkou/kinjinkou/jinkoujue/jinkoujue_api.py·vendor/kinastro/astro/fendjing/fendjing_calculator.py)+ 全量拷贝层 astropy/tests/test_kentang_display_fast.py + tracked golden/soak_qimen.py | ①奇门请求级 memo(horosa_qimen_req_memo_v1):同参跨函数重复(gangzhi 十余次/请求、zhifu_n_zhishi 15 次、定局链重走 _anchor_solstice)——与 _TLS 同宿的 per-request dict,webqimensrv 设完两个日界开关后 begin(begin 即清:纯函数+键含开关 ⇒ 残留语义无害,免 finally 重排缩进);键不可哈希直通;list/dict 出参浅拷贝;★刻意剔除 _last_shangyuan_before(sxtwl Day 身份键零命中)、★pan_sky_minute 永不入(del sky["中"] 就地变异)。模块尾统一重绑定(config 按名 import 发生在 jieqi 执行完之后 ⇒ 拿到包装体;免逐 def 装饰)。②kin 常量 copy-return 全族(horosa_kin_jiazi_const_v1):六十甲子/分钟表/刻表编译期常量原每请求重建;return list(_C)/dict(cached) 与逐次重建逐字节等价,变异审计降为纵深防御。★勿重复:kinwangji 两树+kinastro/wangji 上游已 @lru_cache、kinastro/bazi 已 _JIAZI_SEQ;★死代码勿改:kinjinkou 平铺 jieqi.py 与 kinastro/astro/sanshi/kinliuren*.py 只被 streamlit app 引用;★kintaiyi.py 嵌套 jiazi_accum 实测 ~µs 级,如实跳过。③display translate(horosa_display_trans_v1):DISPLAY_REPLACEMENTS 全 1字→1字 ⇒ ~125 次全串 replace 换单遍 C 级 str.translate;_DISPLAY_TRANS_OK 全单字守卫(混入多字符自动退回旧循环);等价三层证明=不变量断言(值∩键⊆恒等对{逆})+~6 万例穷举单测+黄金全矩阵。④高纬度限界(horosa_phasis_bounded_v1):heliacal_ut 无上界前搜而采用窗仅 ±7 天 —— ±8.5d 逐小时太阳最深高度 >-1° 预筛(盘内缓存,fail-open)+ HELFLAG_SEARCH_1_PERIOD(被采用事件必在首会合周期:jd≤start+22d<水星 115.9d;找不到=抛=旧 None)。north-hi 整盘 8417→185ms(45×),响应字节不变。⑤fastjson(horosa_fast_json_encode_v1):纯 JSON 树上 json.dumps 全默认参与 jsonpickle.encode(unpicklable=False) 逐字节相等(4/4 探针 EQ)——_FastJsonEncodeShim 仅「开关开+真 jsonpickle+默认参」走快径,TypeError/ValueError 回退原实现(回退零漂移 by construction);compat 桩(ensure_ascii=False)不等价,绝不套快径。验证:全矩阵 3823 例 ZERO DRIFT(全开)+ 五开关合并置 0 复跑 ZERO DRIFT + soak 8×200=1600 请求零漂移;全矩阵墙钟 252.7→96.4s(-62%)。kill:HOROSA_QIMEN_REQ_MEMO / HOROSA_KIN_JIAZI_CONST / HOROSA_DISPLAY_TRANS / HOROSA_PHASIS_BOUNDED / HOROSA_FAST_JSON_ENCODE。 | 恢复:apply.sh §34(2 新 srv 补丁 + 8 kin 补丁 + 1 cp)+ §28 既有 kinqimen 两件/§17 perchart/§30 kinastro_common/§13 webchartsrv 累积补丁已 regen;哨兵+PERF_INVENTORY PY-18..21;gotcha PERF-R10 节。 补丁清单:astropy__webqimensrv.reqMemo.py.patch·astropy__webpredictsrv.fastJson.py.patch·vendor__kintaiyi__jieqi.kinConst.py.patch·vendor__kintaiyi__config.kinConst.py.patch·vendor__kintaiyi__kinliuren.kinConst.py.patch·vendor__kinwuzhao__jieqi.kinConst.py.patch·vendor__kinwuzhao__config.kinConst.py.patch·vendor__shenyishu__shenyishu.kinConst.py.patch·vendor__kinjinkou__jinkoujue_api.kinConst.py.patch·vendor__kinastro__fendjing_calculator.kinConst.py.patch(既有累积已 regen:vendor__kinqimen__jieqi.qimenPerf.py.patch·vendor__kinqimen__config.qimenPerf.py.patch·astropy__kentang__kinastro_common.stubDunderGuard.py.patch·astropy__perchart.chartMemo.py.patch·astropy__webchartsrv.xuanshiWarmup.py.patch)。 |
| 41 | 后端 Java 双项(PERF-R10 Ship4):astrostudysrv/astrostudy/.../helper/CacheHelper.java + .../AstroHelper.java + desktop_installer_bundle/electron/service-manager.js(两条 -D) | ①comm 缓存 -D 豁免(cachehelper.needcache):CacheHelper.NeedCache 原只走 PropertyPlaceholder(不读 -D)——桌面无 Redis,/ziwei/birth、/calendar/month、/nongli/time 每 miss 付一次连接异常+JedisPool 重建+2 行错误日志;resolveBoolFlag(先 -D)照抄 ParamHashCacheHelper 惯用法,启动器传 -Dcachehelper.needcache=false = 「今日无 Redis 的净效果」(恒 miss 必算),输出字节全等;Web/Mac 零变。②chart 家族内层缓存跳过+撞键根治(astrohelper.skip.inner.cached.paths):外层已被 ParamHashCacheHelper 包裹的五路径("/"、/chart13、/chart12、/india/chart、/jieqi/year)内层 request() 再包一次 = 同 scope 同参写同一个缓存文件——冷路径双付 hash+persistable+同步文件写;且 /chart13 等外层装配后覆写同一文件,装配 lambda 在两写之间抛异常时下个请求会把未装配的 Python 原始响应当外层命中返回(正确性隐患)。保守枚举绝不通配(/jieqi/birth、/predict/* 的内层是唯一缓存,一个不跳);默认 false=Mac 零变,桌面 -D 开。验证:三臂 A/B(LIVE 旧 jar / 新 jar+双旗标 / 新 jar 无旗标)四端点冷+温响应逐字节相等;boundless/basecomm nested jar sha==LIVE;两 marker 串编译入 class。 | 恢复:apply.sh §35(BACKEND → jar 重建:cd astrostudy→astrostudycn→astrostudyboot);哨兵(class 内属性串)+PERF_INVENTORY JV-9/JV-10;gotcha PERF-R10 节。 补丁清单:astrostudy__CacheHelper.needcacheSysprop.java.patch·astrostudy__AstroHelper.skipInnerCache.java.patch。 |
| 42 | 前端缓存统一 + 温启现场恢复(PERF-R10 Ship5/6):services/_kentangResultCache.js(files/ 层,L3)+ services/qizheng.js + services/_requestCache.js + utils/bootChartRestore.js(新,files/ 层)+ models/{astro,app}.js + utils/perfFlags.js + 壳层 electron/{main,service-manager}.js(S1/S3) | ①kentang L3(horosa_kentang_l3_v1):kentang 族裸 fetch 不经 request(),重启后内存 LRU 全冷 —— 给 _kentangResultCache 挂 IndexedDB 持久位,信封与 requestDedupe L3 同款(rev 掺 &rv= runtime 版本封陈果、TTL 24h、空/错不入、键 kt.| 端口无关);命中回填内存;clear 只清内存。②moira 稳定键(horosa_moira_stable_key_v1):body 里 chartObj.chartId 每盘随机 ⇒ 旧键(整 body)同参永不命中且先付 ~420KB stringify;响应由 (params,transitParams) 完备决定(chartObj 是 params 的纯函数,黄金已证)⇒ cachedPost 增 cfg.key 显式键,moira 传双 params 签名;仍在 PREFETCH_FORBIDDEN 不预取。③bootChartRestore(horosa_boot_chart_restore_v1,owner 拍板默认开):温启后 chartObj:null、fields=「此刻」,首屏空白;快照 = fetchByChartData 的 record 形状(birth/ad/zone/lat/lon/name/pos + memo×8 + RECORD_FIELDS_RESTORE_MANIFEST 全部选项键 —— 键清单单一事实源),出盘 settle 经 deferredStorage 空闲落盘;app setup 末尾重放 fetchByChartData(与手动载入命盘逐字节同管线)——L3 命中时后端未就绪也能先画=「秒开上次工作现场」。7 天窗+桌面壳判定+fieldsEpoch 兜底。④壳层 S1(horosa_early_nav_v1/horosa_l3_rev_wire_v1):starting-* 状态即发布端口对/payloadId → 渲染器 spawn 即导航(HOROSA_RENDERER_EARLY_NAV,默认开;端口重试换对 → 强制重导航守卫);getRendererIndexUrl 挂 &rv=payloadId[:16](L3 陈果窗此前一直开着:壳从不发 rv ⇒ rev 恒基础值)与 early=1;ready → 注入 __horosaBackendConfirmed(boot 门全根放行,免最后一轮 350ms 探活)。⑤壳层 S3(horosa_bg_throttle_off_v1):backgroundThrottling 默认 true 会在最小化/遮挡时冻结预取泵并使双 rAF 打点失真 —— 置 false,HOROSA_RENDERER_BG_THROTTLE=1 恢复默认。 | 恢复:apply.sh §33b(3 cp + 3 apply)+ §22 astro / perfFlags 累积补丁已 regen;壳层改动经 HARNESS_MANIFEST 盘点(electron/* 可自 app.asar 恢复);哨兵+PERF_INVENTORY FE-24/FE-25/FE-26/JV-11/JV-12;gotcha PERF-R10 节。 补丁清单:src__models__app.bootChartRestore.js.patch·src__services__qizheng.moiraStableKey.js.patch·src__services___requestCache.cfgKey.js.patch。★v3.5.1 收敛注:①kentang L3(_kentangResultCache.js)已被上游 utils/kentangCache.js fetch 级三层缓存整体取代(信封同款 kt-v1|rv,原生消费本行 ④ 的 &rv= 接线)——文件与 files/ 双份删除、开关 kentangL3 退役;②bootChartRestore/moira 稳定键/壳层 S1/S3 原样存续。 |
| 43 | 五兆自动揲筮缓存守卫(v3.5.1 收敛轮):上游 astrostudyui/src/utils/kentangCache.js 上的 Windows-ahead 补丁 | 上游把 kentang 直连族收编进 fetch 级三层缓存,但覆盖矩阵把 wuzhao 整体标 deterministic —— 自动揲筮档(mode≠ganzhi 且 !manual)payload 无 seed、服务端 random.randint,缓存会把随机揲筮钉死(同 body 重卦返回冻结旧卦=随机起课语义破坏)。守卫加在唯一缓存层 payloadCacheable(干支法/手动折竹照常享缓存);金标 kentangCacheWuzhaoGuard.test.js(files/ 层)3 例。恢复:apply.sh §33c(1 apply + 1 cp);哨兵 horosa_wuzhao_random_guard_v1;PERF_INVENTORY FE-28。 补丁清单:src__utils__kentangCache.wuzhaoGuard.js.patch。 |
| 44 | chartFree 契约哨兵 CRLF 免疫(v3.5.1 收敛轮;可上游化):astrostudyui/src/utils/__tests__/chartFreeContract.test.js | 该哨兵剥注释后 grep「props.value/chartObj 消费」;剥注释按 split('\n') 逐行 + /\/\/.*$/ —— JS 正则 . 不匹配 \r,CRLF 工作树上该正则整段失配 ⇒ 注释剥不掉 ⇒ 哨兵被声明处自己的契约注释触发假红(autocrlf checkout 实测栽过;同因还会弱化任何逐行 $ 锚判据)。改 split(/\r?\n/),LF 上行为逐字节不变。配套:本轮已把 320 个被历次 checkout 翻成 CRLF 的前端源文件归一回 LF(与 Mac 字节口径一致)。恢复:并入 §25b 的 chartFreeContract 累积补丁(同文件单补丁纪律 #48);哨兵并入既有键(#29)。 补丁清单:src__utils__chartFreeContract.pathsep.test.js.patch(累积:pathsep + stripCrlf 双 marker)。 |
| 45 | StartupGate 桌面壳温启数字行(PERF-R11 T3c;可上游化):上游 astrostudyui/src/components/common/StartupGate.js 上的 Windows-ahead 补丁 | 温启窗口(工作区可见→后端就绪 ~0.6s→4s)此前无任何数字反馈 —— 组件计时器 6s 阈值才显示,温启永远到不了。~40 行 Electron-only 分支:readDesktopStartupCfg 读 window.horosaDesktop.getBootstrapConfig()(startupUx / runtimeStartedAtMs / expectedTotalMs,preload 既有 sendSync 零新桥)→ 卡片 t=0 起小字「已用时 x.x 秒 ・ 以往约 y.y 秒」(100ms 子表,锚到壳层起点:首帧即显示 pre-nav 已消耗秒数而非 0.0 起跳)。Mac/网页零影响:无 window.horosaDesktop = 死分支,渲染逐字节不变;上游化建议:分支可原样上 Mac(Tauri 侧如要等价能力,把锚点与典型值经其 invoke 通道下发即可)。壳侧 kill:HOROSA_LOADING_UX=0 → startupUx:false → 整行退场。金标 startupGateDesktopElapsed.test.js(files/ 层)5 例:死分支/锚点非零/expected 缺失/kill 退场/桥抛错不炸。恢复:apply.sh §36(1 apply + 1 cp);哨兵 horosa_startupgate_desktop_elapsed_v1;PERF_INVENTORY FE-29。 补丁清单:astrostudyui__StartupGate.desktopElapsed.js.patch。 |
| 46 | components/direction/AstroDirectMain.js(既有 warmPd 补丁并入,可上游化) | 星运页两修(GitHub issue #59「3.6.0 进入星运台时卡死」)。 ①主限天球改懒加载(horosa_pdsphere_lazy_v1):本页静态 import 主限天球 → PDSphereEngine → three + OrbitControls,模块求值期就把 3D 引擎(产物 vendors-gl ≈860KB + 引擎 ~100KB)拖进星运页加载关键路径;而本页默认停在「主限法」表格页,27 个子页签里只有天球用 3D —— 用户从不打开天球也付全额解析成本,慢机上主线程长时间不响应(Windows 弹「程序无响应」)。改 React.lazy + Suspense + TechniqueErrorBoundary(照抄本仓 pages/index.js 既有 3D 惯用法「babylon 系重组件不入主包」),并在挂载后空闲期 requestIdleCallback 预热一次 —— 不打开=零成本,打开时通常已就绪。②主限法请求维度补齐:PERF-R8 把请求构造抽成模块级纯函数、类方法改纯委托,此后上游 v3.6.0 大扩容主限法给类方法加了 6 个新维度,我方复制体没跟上 ⇒ Windows 侧每次请求都悄悄丢掉 pdProjection/pdFrame/pdFramework/pdParallel/pdRaptParallel/termsVariant,工具条上的「方向/盘面宫制/平行/急动/界系」全成死开关(构建期零告警,与 #79 同族的静默类)。已按上游现行实现逐字补齐。两修均同源于上游(Mac 亦如此),建议同步上游。 | patches/src__components__direction__AstroDirectMain.warmPd.js.patch(regen,round-trip 验过);apply.sh §31 既有行不变;哨兵 horosa_pdsphere_lazy_v1 / React.lazy / pdProjection: desired.pdProjection 等五针;新门 PD request builder completeness(按请求身份键做完备性核,负向自证过) |
| 47 | components/commtools/CommToolsMain.js(既有 perfR9 补丁修正)+ scripts/check-symbol-binding.cjs(门升 v2) | 「小工具」面板打开即 ReferenceError(issue #59 二次报告,Windows 独有)。 本文件的 <FreezeSubTab> 用法来自我方 PERF-R9 子页签冻结改造(Mac 侧该文件 FreezeSubTab 出现 0 次 —— 纯 Windows-ahead),而 overlay 补丁从生成那天起就只加了用法、从没加过 import ⇒ 每次 apply.sh 复叠都忠实重放该缺陷,regen 还会把它 round-trip 成「真理」;用户打开小工具抽屉即 ReferenceError: FreezeSubTab is not defined。三重门为何全放行:①符号绑定门(#79 的产物)判据正确但范围只盖 src/utils/ 导出,而 FreezeSubTab 是 components/comp/FreezeInactive.js 导出的组件;②umi 不 mount 该抽屉;③webpack 对自由标识符零告警。修:补 import + regen 补丁固化(否则下轮复发)+ 门升 horosa_symbol_binding_gate_v2(收集面 utils/ → utils/+components/,name→Set(定义方)+ 自引用豁免;扫描面 1037→3239 个导出),负向自证:删回 import 精确报 CommToolsMain.js:96 FreezeSubTab。 | patches/src__components__commtools__CommToolsMain.perfR9.js.patch(regen,已含 import 行);apply.sh §行不变;门 frontend utils symbol binding 自身即哨兵(gotcha #84) |
| 48 | astrostudyui/src/utils/__tests__/heavyEngineImportGraph.test.js(上游 v3.6.2 新护栏的 Windows 移植适配,可上游化) | 上游本轮新增「重引擎不得进入页面静态 import 图」依赖图护栏(自动抓全部 lazy 页面遍历,含两条自证)。其豁免表 ENGINE_HOSTS 的键是 POSIX 写法(components/astro3d/AstroChartMain3D.js),而 path.relative 在 Windows 返回反斜杠分隔 ⇒ ENGINE_HOSTS[rel] 恒查不中 ⇒ 合法的引擎宿主豁免失效,3D 星盘页被误报违规(macOS 恒绿、只在 Windows 假红)。修:引入 relPosix() 归一后再查表与拼链路(5 处 path.relative(SRC,..) 全改),产品行为零影响、护栏语义零放宽(两条自证仍绿:静态引 three 必命中、import() 处必剪枝)。与 chartFreeContract / quickDockContract 同 marker 同病理。 | patches/src__utils____tests____heavyEngineImportGraph.winPathsep.test.js.patch(regen,round-trip 验过);apply.sh 紧随既有两条 pathsep 行;哨兵 horosa_win_pathsep_posix_v1 + relPosix |
| 49 | components/zeri/TianxingElectionMain.js(v3.7.0 新技法「天星择日」的 P5 观测终点,可上游化) | 上游 v3.7.0 新增主导航页「择日」(navigationPages 键 zeri,宿主 ZeriMain→TianxingElectionMain,中栏经 DivinationChartShell 画 /chart 盘)。Windows 的 P5 观测覆盖门要求每个技法键有 markPanelReady 终点,而 Mac 树无观测层(0 文件)⇒ 新页天然缺口。修:照 ElectionMain 先例在 renderRight 收到壳层 chart 落定时打 markPanelReady('zeri')(_readyChart 引用去重;征象扫描 runScan/explain 是显式批量求值,刻意不打点,防秒级扫描配到上次交互起点污染 p95)。P6 侧:征象搜索是区间扫描型、无时间步进主轴,登记为结构性豁免(CONTRACT_EXEMPTIONS p6-prefetch zeri 行);/chart 底盘由内建 chartTasks 覆盖,electionscan 端点缓存由上游 cachedKentangFetch 三层承载。 | patches/src__components__zeri__TianxingElectionMain.panelReady.js.patch(round-trip 验过);apply.sh §32 段尾;SENT 钉 markPanelReady('zeri') 字面量(#75 归属键) |
| 50 | astropy/websrv/webchartsrv.py(v3.7.0 覆盖修 W0d:electionscan 预装挪门后,可上游化 —— Mac trusted 温启同样受益) | v3.7.0 把 electionscan 加进 CORE_SERVICE_SPECS ⇒ 其模块链(import swisseph+flatlib.ephem.swe+~4.2k LOC)进了门前 tier-3 串行预装,同机温启 3937→5042ms(+1.1s,owner 实感实证;PY-15「门前 prewarm=直接推迟启动门」反模式重演)。修:POST_GATE_CORE_PREWARM_KEYS={'electionscan'} 数据化白名单 + 门后段首位装载(先于 kentang modules/xuanshi);ping 短路不受影响,首个真 /scan 最坏付一次温 import(落在显式搜索点击上);饿加载态 isinstance 守卫天然 no-op。制度门双轨:SENT 三针 + verify_all_services horosa_pregate_prewarm_budget_v1(门前键集==钉死白名单,新 CORE 键必须显式决定门前/门后,负向自证:假 15th 键/electionscan 回门前/解析塌缩 全红)。 | 并入 patches/astropy__webchartsrv.xuanshiWarmup.py.patch(regen round-trip 过);apply.sh guard 升 horosa_electionscan_postgate_prewarm_v1(#48);kill=HOROSA_ELECTIONSCAN_POSTGATE=0 回 v3.7.0 原样 |
- #1–#5 are robust (full copies / idempotent ops). #6–#8 are patches against Mac
1c463718/270eb01e; if a future Mac change touches the same region the patch may not apply cleanly —apply.shwarns, and the exact changes are also visible in the.patchfiles (diff--- Mac ... +++ Windows-adapted). Re-apply by hand if so. - #8 is BACKEND (boundless) → it needs a jar rebuild every time it's (re-)applied.
apply.shonly patches the source;prepare:runtime's auto-build pulls boundless from.m2(stale) so it will NOT pick the patch up. Afterapply.sh, rebuild per SKILL gotcha #5:boundless install → astrostudy install → astrostudycn install → astrostudyboot clean package, then copytarget/astrostudyboot.jartolocal/workspace/runtime/windows/bundle/. Recommended: also land this fix upstream in Mac's boundless (it's a cross-platform proxy bug, not truly Windows-only — Mac's launcher sets the same-Djava.net.useSystemProxies=true); once Mac hasisLoopbackTarget,apply.sh's marker guard makes this patch a harmless no-op and the two trees converge. - The launcher mirror (service-manager.js port-retry etc.) is NOT here — it lives in the tracked
desktop_installer_bundle/electron/service-manager.js, so it's already git-safe. - When you ADD a new Windows adaptation in a future sync: drop the file/patch here, wire it into
apply.sh, add arelease_selfcheck.pysentinel, and note it in this table (+ SKILL.md gotcha #16).