Files
road-compiler/.trellis/tasks/08-28-junction-control-offsets/prd.md
que01 c7425f5ed4 feat: make crosswalk and stop-line offsets solvable
Step 1 of the control-marking task, and deliberately server-only: no handle is
drawn yet. This project already shipped a range handle for `profile.interval`,
which compileGeometry ignores, so the control dragged and changed nothing. The
consumer comes first now.

Two kinds join the taxonomy — `junction-crosswalk-inset` and
`junction-stop-line-offset`, both on the existing `junction-approach` anchor. The
solver writes them onto the approach entry, `applyDirectJunctionPlans` carries
them onto the compiled approach, and `compileControlMarkings` reads them in place
of the module constants it used for every junction. They move markings without
reshaping the junction, so unlike width and cutback they deliberately do not
trigger a boundary recompute.

`applyJunctionConstraint` becomes an explicit switch. Its trailing `else` had
meant every kind that was not approach-width fell through to the cutback
validator, so a new kind would have been silently validated and written as a
cutback. The same non-exhaustive shape in the test fixture's `valueFor` is fixed
the same way, and now throws for an unnamed kind rather than answering with a
corner radius.

design.md's taxonomy is updated with it — a test asserts the two cannot drift,
which is what caught the omission.

Measured on a 41-road workspace with 8 crossings: both constraints change their
marking geometry, neither drags the other, and out-of-range blocks instead of
clamping. That measurement is not in the suite: the synthetic junction resolves
`junction_inset_m` to 0 because its crossing never binds to a plan, and the
committed OSM fixture has no crossings at all. The tests assert the wiring the
handles will depend on — values reaching the approach entry, distinct branches,
blocking diagnostics — and the gap is recorded in the test itself.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-28 15:01:36 +08:00

92 lines
4.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 路口控制标线偏移:斑马线与停止线可拖
父任务:`.trellis/tasks/08-26-direct-manipulation-road-editor`
这是「更多元素可直接操纵」方向的第一个元素类型。
## 为什么先做这一类
`08-26-direct-edit-map-editor` 交付了完整的直接操纵链路——
manifest → 命中 → 拖拽 → 投影 → 钳位 → ghost → 防抖 → 乱序仲裁 → 预览 → 保存 → 撤销。
这条链路**不认识具体元素**,只认 `kind + anchor + axis + value`。所以加一类元素
本应是「定义一个 kind + 一个写入器」,而不是造新子系统。
选斑马线/停止线打头因为可行性已实测确认2026-08-28
```js
611 model = modelWithDirectEditProfiles(model, options.directEdit);
613 const junctionPlans = compileJunctionPlans(model, options, diagnostics);
614 applyDirectJunctionPlans(junctionPlans, options.directEdit); // 直接编辑已合并
768 const controls = compileControlMarkings(model, lanes, diagnostics, junctionPlans);
1299 const plan = junctionPlans.get(junctionNodeId); // 已按路口取到 plan
```
junction 级约束**已经能流到控制标线的生成**,管线是通的。挡路的只是模块级常量:
```js
const STOP_LINE_OFFSET_METERS = 2.7;
const CROSSWALK_JUNCTION_INSET_METERS = 1.5;
```
它们全局一个值、不按进口区分。把常量换成「先查进口配置,没有再用默认」即可。
这与区间那件事性质不同:区间要求横断面**沿路变化**,是几何管线重构;这里只是
在已经拿到手的 `plan` 上多读一个字段。
## 强制顺序编译器优先UI 最后
`08-26-direct-edit-map-editor` 交付了一个拖了没有任何效果的区间手柄,因为
`compileGeometry()` 从不读 `profile.interval`——UI 先于消费者建成。
`research/interval-not-applied.md`
本任务反过来:**第 1 步不产出任何 UI**,只证明编译器真的消费这个参数。
第 1 步的门禁不过就停,不进入第 2、3 步。
## 范围
### 1. 服务端打通并证明(无 UI
- 新增两个约束 kind`junction-crosswalk-inset``junction-stop-line-offset`
锚点均为 `junction-approach`(求解器已建模该锚点)。
- `native-road-edits.js`schema 校验与取值范围。
- `direct-edit-solver.js``applyJunctionConstraint()` 把值写进该进口的 plan 条目。
- `native-road.js``compileControlMarkings()` 读进口配置,缺省时退回既有常量。
### 2. 手柄目标泛化
`EditHandle` 声明「一次手势写到哪里」,客户端在提交处分岔。
验收标准是:**加第 N+1 种元素时不需要修改任何已有<E5B7B2><E69C89>柄代码**。
### 3. 手柄与拖拽
选中路口后斑马线、停止线上出现手柄,沿进口方向拖动调整其距路口的距离。
复用既有链路,不新增预览或保存机制。
## 不做
- 不做红绿灯(属「有位姿的实体资产」,需要不同的写入器与相对锚点)
- 不做箭头(需先定交互语义:拖箭头应改行驶动作,而非移动图标)
- 不做区间生效,方块手柄继续隐藏
- 不改 `STOP_LINE_MAX_APPROACH_DISTANCE_METERS` 等与放置**是否成立**有关的判据,
只改放置**位置**
## 验收标准
### 第 1 步(门禁,不过则停)
- [ ]`junction-crosswalk-inset` 约束编译出的斑马线坐标,与不带时**不同**
- [ ]`junction-stop-line-offset` 约束编译出的停止线坐标,与不带时**不同**
- [ ] 无约束时输出与当前 main **逐字节相同**fixture 基线无 diff
- [ ] 越界取值产生阻塞诊断,不静默钳制
- [ ] 约束经保存与重新加载后状态为 `exact`
### 第 2、3 步
- [ ] 选中路口后斑马线与停止线出现手柄,拖动可改变其距路口距离
- [ ] 手柄位置由钳位值反算,拖到极限停在边界
- [ ] 新增元素类型未修改既有手柄代码(以 diff 佐证)
- [ ] `npm run format:check``npm run test``npm run test:client``npm run test:client:unit``npm run build` 全绿
## 顺序依赖
无前置。后继:红绿灯位姿与箭头语义,二者都依赖第 2 步的目标泛化。