Accept soft Scattermind action fields
This commit is contained in:
@@ -51,7 +51,7 @@ Candidate items may include optional 1–10 `rankerHints` (`value`, `effort`, `c
|
|||||||
|
|
||||||
Candidate trace note: candidate-level `sourceItemId` / `traceId`, `sourceTitle` / `lensTitle`, and `sourceExcerpt` / `sourceQuote` are preserved in ranked items, `buildOrderDetails`, and `handoff.itemTrace`. Lens-only Build Order text is also split into deterministic `concept-map.lenses.channel#N` source IDs with the original labelled sentence carried as `sourceQuote`, so pasted paid Concept Maps remain traceable even without explicit candidate objects. String items in laned Build Order arrays now also receive deterministic section-local source IDs such as `concept-map.buildOrder.validateNext#1` and carry the original string as `sourceQuote`, so simple Scattermind exports stay addressable downstream instead of becoming anonymous `feature-1` rows. Ranker also accepts the current Scattermind storage-row shape with `referenceCode`, `ideaText`, `context`, and string-valued `fullReadingJson` / `full_reading_json`; it expands the saved paid Concept Map before ranking so operators do not have to hand-copy lenses out of Appwrite rows. It also accepts private reading/API envelopes with object-valued `reading` / `fullReading` and `glimpse`, preserving `referenceCode` and `initialPrompt` while treating `reading` as the paid Concept Map. Stringified bridge envelopes in fields such as `rankerInput`, `rankerBridge`, `rankReady`, `bridgePayload`, or `continuationPlan` are expanded the same way, so Appwrite/string-copy handoffs do not have to be manually unwrapped before ranking. If the row only has free Snapshot data (`glimpseJson` / `glimpse_json` / `snapshotJson`), Ranker expands that Snapshot into a minimal continuation order: one manual proof plus the first evidence question, with the Snapshot reference code/title preserved for provenance. The public paste form mirrors this: a prose-wrapped/fenced Appwrite row paste stays intact for the API instead of unwrapping the stringified reading into nonsense fields. The decision `brief.quickGlance.sourceTrace` now repeats the winning item's source section/id/title/quote, and both `brief.source.originalPromptExcerpt` / `handoff.source.originalPromptExcerpt` or, when the original prompt is unavailable, `sourceSummaryExcerpt` carry the source context into the bridge handoff.
|
Candidate trace note: candidate-level `sourceItemId` / `traceId`, `sourceTitle` / `lensTitle`, and `sourceExcerpt` / `sourceQuote` are preserved in ranked items, `buildOrderDetails`, and `handoff.itemTrace`. Lens-only Build Order text is also split into deterministic `concept-map.lenses.channel#N` source IDs with the original labelled sentence carried as `sourceQuote`, so pasted paid Concept Maps remain traceable even without explicit candidate objects. String items in laned Build Order arrays now also receive deterministic section-local source IDs such as `concept-map.buildOrder.validateNext#1` and carry the original string as `sourceQuote`, so simple Scattermind exports stay addressable downstream instead of becoming anonymous `feature-1` rows. Ranker also accepts the current Scattermind storage-row shape with `referenceCode`, `ideaText`, `context`, and string-valued `fullReadingJson` / `full_reading_json`; it expands the saved paid Concept Map before ranking so operators do not have to hand-copy lenses out of Appwrite rows. It also accepts private reading/API envelopes with object-valued `reading` / `fullReading` and `glimpse`, preserving `referenceCode` and `initialPrompt` while treating `reading` as the paid Concept Map. Stringified bridge envelopes in fields such as `rankerInput`, `rankerBridge`, `rankReady`, `bridgePayload`, or `continuationPlan` are expanded the same way, so Appwrite/string-copy handoffs do not have to be manually unwrapped before ranking. If the row only has free Snapshot data (`glimpseJson` / `glimpse_json` / `snapshotJson`), Ranker expands that Snapshot into a minimal continuation order: one manual proof plus the first evidence question, with the Snapshot reference code/title preserved for provenance. The public paste form mirrors this: a prose-wrapped/fenced Appwrite row paste stays intact for the API instead of unwrapping the stringified reading into nonsense fields. The decision `brief.quickGlance.sourceTrace` now repeats the winning item's source section/id/title/quote, and both `brief.source.originalPromptExcerpt` / `handoff.source.originalPromptExcerpt` or, when the original prompt is unavailable, `sourceSummaryExcerpt` carry the source context into the bridge handoff.
|
||||||
|
|
||||||
Soft Scattermind labels are accepted at the bridge boundary so Scattermind does not need to use harsh verdict copy in its own product surface. Lens text can say `Continue first`, `Make tangible`, `Try next`, `Evidence next`, `Hold for later`, or `Set aside`, with either colon or reader-friendly dash separators (`Continue first: …`, `Continue first — …`, `Evidence next - …`). Build Order objects and direct bridge/envelope sections can use matching camel/snake-case keys such as `continueFirst`, `evidenceNext`, `holdForLater`, and `setAside`. Ranker maps those to `doFirst / validateNext / defer / park` while preserving the softer original label in `sourceQuote` or candidate source trace. Ranker also accepts softer continuation envelopes named `rankerBridge`, `continuation`, or `continuationPlan`, candidate arrays named `possibleNextMoves`, `suggestedNextMoves`, `recommendations`, or `opportunities`, laned `buildOrderPreview` / `build_order_preview` objects, and evidence-question fallback arrays (`evidenceQuestions` / `evidence_questions`, `decisionQuestions`, `questionsToAnswer`, `followupQuestions`). If a paid Concept Map has no labelled Build Order/action threads but does include `closing_note` / `closingNote` plus decision questions, Ranker treats the closing note as the active 48-hour Do first move and keeps the questions in Validate next. This lets Scattermind pass reader-friendly Concept Map copy without renaming everything into software-feature language.
|
Soft Scattermind labels are accepted at the bridge boundary so Scattermind does not need to use harsh verdict copy in its own product surface. Lens text can say `Continue first`, `Make tangible`, `Try next`, `Evidence next`, `Hold for later`, or `Set aside`, with either colon or reader-friendly dash separators (`Continue first: …`, `Continue first — …`, `Evidence next - …`). Build Order objects and direct bridge/envelope sections can use matching camel/snake-case keys such as `continueFirst`, `evidenceNext`, `holdForLater`, and `setAside`. Ranker maps those to `doFirst / validateNext / defer / park` while preserving the softer original label in `sourceQuote` or candidate source trace. Ranker also accepts softer continuation envelopes named `rankerBridge`, `continuation`, or `continuationPlan`, candidate arrays named `possibleNextMoves`, `suggestedNextMoves`, `recommendations`, or `opportunities`, laned `buildOrderPreview` / `build_order_preview` objects, and evidence-question fallback arrays (`evidenceQuestions` / `evidence_questions`, `decisionQuestions`, `questionsToAnswer`, `followupQuestions`). Direct candidate objects may use reader-friendly prose keys like `text`, `content`, `summary`, `why_it_matters`, `evidence_to_collect`, `first_proof_step`, `green_flag`, and `red_flag`; Ranker normalizes those into title, value, evidence, next-step, success, and kill-signal fields so Scattermind does not have to rename paid Concept Map language into software-feature jargon. If a paid Concept Map has no labelled Build Order/action threads but does include `closing_note` / `closingNote` plus decision questions, Ranker treats the closing note as the active 48-hour Do first move and keeps the questions in Validate next. This lets Scattermind pass reader-friendly Concept Map copy without renaming everything into software-feature language.
|
||||||
|
|
||||||
Lane safety note: explicit Scattermind `defer` / `park` hints are hard rails, not mild suggestions. Source `nonGoals` / `avoid` guardrails are also hard enough to keep conflicting candidates out of Do first / Validate next even when their local scoring hints look attractive; soft guardrail language such as “this is not a dashboard” or “keep auth/billing/workspaces out until proof” is promoted into non-goals, not merely background context. Ranker now also infers a light `ideaRoute` from the Scattermind source text and carries it in `input.decisionContext` / `handoff.decisionContext`; for game concepts it automatically adds anti-SaaS non-goals so account/dashboard/workspace/subscription candidates cannot win a playable-prototype build order just because they were phrased loudly. The result will mark the lane source as `source-non-goal` so the handoff can explain that the candidate needs guardrail resolution before active work. Handoff `source.requiresSourceTrace` is true only when a real source artifact/title is present; plain idea-only ranking still warns about a missing artifact ID when it carries prompt provenance, but it does not spam source-section/evidence warnings meant for Scattermind artifacts. Handoff `readiness` now gives downstream bridge consumers a deterministic gate: `ready`, `usable-with-warnings`, `needs-source-context`, or `blocked`, with blockers and next checks for missing evidence, source trace, duplicate IDs, or active source-non-goal conflicts. Handoff `activeSlice` (`ranker-active-slice-v1`) is the compact machine-readable continuation unit: one active item, its proof/evidence/success/kill signals, source anchor, held-back items, readiness status, and the rule that only this slice is build-ready. For tired first-screen users, `brief.decisionReceipt` repeats the one active move, first proof step, evidence question, held-back items, source anchor, and the handoff rule that only Do first is active; use it as the compact result strip before showing the full lane board. For low-friction handoff, `/api/rank-feedback` also detects a raw Scattermind/Concept Map JSON object pasted into `idea`, `ideaText`, `optionsText`, or wrapper keys such as `payload`; it expands that object before ranking and reports `input.embeddedPayloadSource` so the public form can accept copy/paste exports without a custom import screen. Exact free Snapshot JSON (`working_name`, `restated_idea`, `lenses.shape`, `questions_to_sit_with`, `reference_code`) is rankable too: Ranker derives a manual proof active slice plus evidence questions, carrying the Snapshot reference code/title into provenance so a Snapshot-only handoff does not need a paid Concept Map before it can produce a useful build order. If a Concept Map only carries `questions_to_sit_with` / `questionsToSitWith` / `openQuestions` and no explicit build-order lanes or action threads, Ranker converts those questions into Validate-next evidence actions with source trace instead of pretending they are software features.
|
Lane safety note: explicit Scattermind `defer` / `park` hints are hard rails, not mild suggestions. Source `nonGoals` / `avoid` guardrails are also hard enough to keep conflicting candidates out of Do first / Validate next even when their local scoring hints look attractive; soft guardrail language such as “this is not a dashboard” or “keep auth/billing/workspaces out until proof” is promoted into non-goals, not merely background context. Ranker now also infers a light `ideaRoute` from the Scattermind source text and carries it in `input.decisionContext` / `handoff.decisionContext`; for game concepts it automatically adds anti-SaaS non-goals so account/dashboard/workspace/subscription candidates cannot win a playable-prototype build order just because they were phrased loudly. The result will mark the lane source as `source-non-goal` so the handoff can explain that the candidate needs guardrail resolution before active work. Handoff `source.requiresSourceTrace` is true only when a real source artifact/title is present; plain idea-only ranking still warns about a missing artifact ID when it carries prompt provenance, but it does not spam source-section/evidence warnings meant for Scattermind artifacts. Handoff `readiness` now gives downstream bridge consumers a deterministic gate: `ready`, `usable-with-warnings`, `needs-source-context`, or `blocked`, with blockers and next checks for missing evidence, source trace, duplicate IDs, or active source-non-goal conflicts. Handoff `activeSlice` (`ranker-active-slice-v1`) is the compact machine-readable continuation unit: one active item, its proof/evidence/success/kill signals, source anchor, held-back items, readiness status, and the rule that only this slice is build-ready. For tired first-screen users, `brief.decisionReceipt` repeats the one active move, first proof step, evidence question, held-back items, source anchor, and the handoff rule that only Do first is active; use it as the compact result strip before showing the full lane board. For low-friction handoff, `/api/rank-feedback` also detects a raw Scattermind/Concept Map JSON object pasted into `idea`, `ideaText`, `optionsText`, or wrapper keys such as `payload`; it expands that object before ranking and reports `input.embeddedPayloadSource` so the public form can accept copy/paste exports without a custom import screen. Exact free Snapshot JSON (`working_name`, `restated_idea`, `lenses.shape`, `questions_to_sit_with`, `reference_code`) is rankable too: Ranker derives a manual proof active slice plus evidence questions, carrying the Snapshot reference code/title into provenance so a Snapshot-only handoff does not need a paid Concept Map before it can produce a useful build order. If a Concept Map only carries `questions_to_sit_with` / `questionsToSitWith` / `openQuestions` and no explicit build-order lanes or action threads, Ranker converts those questions into Validate-next evidence actions with source trace instead of pretending they are software features.
|
||||||
|
|
||||||
|
|||||||
@@ -238,6 +238,48 @@ try {
|
|||||||
assert.equal(snakeDecisionContext.buildOrder.doFirst[0], 'copyable-slice');
|
assert.equal(snakeDecisionContext.buildOrder.doFirst[0], 'copyable-slice');
|
||||||
assert.equal(snakeDecisionContext.handoff.readiness.status, 'ready');
|
assert.equal(snakeDecisionContext.handoff.readiness.status, 'ready');
|
||||||
|
|
||||||
|
const softActionObjectResponse = await fetch(`${base}/api/rank-feedback`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
rankerBridge: {
|
||||||
|
sourceName: 'Scattermind',
|
||||||
|
artifactId: 'concept_map_soft_action_objects',
|
||||||
|
snapshotTitle: 'Soft action object handoff',
|
||||||
|
originalPrompt: 'Clarify a practical continuation without forcing software-feature field names.',
|
||||||
|
context: 'Solo builder. Avoid account dashboards before manual proof.',
|
||||||
|
nextActions: [
|
||||||
|
{
|
||||||
|
id: 'manual-proof-text-item',
|
||||||
|
text: 'Run one manual paid-style teardown from the Concept Map',
|
||||||
|
why_it_matters: 'This proves the continuation value before any app surface.',
|
||||||
|
evidence_to_collect: 'Will one tired builder act from the teardown without another explanation?',
|
||||||
|
first_proof_step: 'Send the teardown to one real builder and ask what they would do first.',
|
||||||
|
green_flag: 'They can name the next move and why it wins.',
|
||||||
|
red_flag: 'They ask for a dashboard instead of acting on the decision.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'saved-account-dashboard',
|
||||||
|
content: 'Saved account dashboard for every Concept Map',
|
||||||
|
evidence_to_collect: 'Would users want this later?',
|
||||||
|
ranker_hints: { value: 10, effort: 1, confidence: 9, urgency: 9, risk: 1 },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
assert.equal(softActionObjectResponse.status, 200);
|
||||||
|
const softActionObject = await softActionObjectResponse.json();
|
||||||
|
assert.equal(softActionObject.input.provenance.artifactId, 'concept_map_soft_action_objects');
|
||||||
|
assert.equal(softActionObject.buildOrder.doFirst[0], 'manual-proof-text-item');
|
||||||
|
assert.equal(softActionObject.ranked.find(item => item.id === 'manual-proof-text-item').title, 'Run one manual paid-style teardown from the Concept Map');
|
||||||
|
assert.match(softActionObject.ranked.find(item => item.id === 'manual-proof-text-item').factors.evidenceNeeded, /tired builder/);
|
||||||
|
assert.match(softActionObject.handoff.activeSlice.proof.nextStep, /Send the teardown/);
|
||||||
|
assert.match(softActionObject.handoff.activeSlice.proof.successSignal, /next move/);
|
||||||
|
assert.match(softActionObject.handoff.activeSlice.proof.killSignal, /dashboard/);
|
||||||
|
assert.equal(softActionObject.ranked.find(item => item.id === 'saved-account-dashboard').lane.source, 'source-non-goal');
|
||||||
|
assert.equal(softActionObject.handoff.readiness.status, 'ready');
|
||||||
|
|
||||||
const threadGuardrailResponse = await fetch(`${base}/api/rank-feedback`, {
|
const threadGuardrailResponse = await fetch(`${base}/api/rank-feedback`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
|||||||
@@ -1046,15 +1046,15 @@ function nonGoalConflicts(optionText, decisionContext = {}) {
|
|||||||
function normalizeFeatureOption(item, index, fallbackId = 'feature', defaultSourceSection = '', defaultRecommendedLane = '') {
|
function normalizeFeatureOption(item, index, fallbackId = 'feature', defaultSourceSection = '', defaultRecommendedLane = '') {
|
||||||
const rawValue = typeof item === 'string' || typeof item === 'number' ? String(item) : '';
|
const rawValue = typeof item === 'string' || typeof item === 'number' ? String(item) : '';
|
||||||
const raw = rawValue ? { action: rawValue } : objectFrom(item);
|
const raw = rawValue ? { action: rawValue } : objectFrom(item);
|
||||||
const title = cleanText(raw.title || raw.name || raw.action || raw.move || raw.nextMove || raw.next_move || raw.nextStep || raw.next_step || raw.recommendedNextStep || raw.recommended_next_step || raw.experiment || raw.testName || raw.test_name || raw.hypothesis || raw.label || '', 140);
|
const title = cleanText(raw.title || raw.name || raw.action || raw.move || raw.nextMove || raw.next_move || raw.nextStep || raw.next_step || raw.recommendedNextStep || raw.recommended_next_step || raw.experiment || raw.testName || raw.test_name || raw.hypothesis || raw.label || raw.text || raw.content || raw.summary || '', 140);
|
||||||
const proofSteps = cleanTextList(raw.proofSteps || raw.proof_steps || raw.proof || raw.validationSteps || raw.validation_steps || raw.steps || raw.method, 5, 180);
|
const proofSteps = cleanTextList(raw.proofSteps || raw.proof_steps || raw.proof || raw.firstProof || raw.first_proof || raw.validationSteps || raw.validation_steps || raw.steps || raw.method, 5, 180);
|
||||||
const dependencies = cleanTextList(raw.dependencies || raw.blockedBy, 5, 120);
|
const dependencies = cleanTextList(raw.dependencies || raw.blockedBy || raw.blocked_by, 5, 120);
|
||||||
const evidenceNeeded = cleanText(raw.evidenceNeeded || raw.evidence_needed || raw.evidence || raw.test || raw.evidenceQuestion || raw.evidence_question || raw.questionToAnswer || raw.question_to_answer || raw.question || raw.learningGoal || raw.learning_goal || '', 260);
|
const evidenceNeeded = cleanText(raw.evidenceNeeded || raw.evidence_needed || raw.evidenceToCollect || raw.evidence_to_collect || raw.evidence || raw.test || raw.proofQuestion || raw.proof_question || raw.evidenceQuestion || raw.evidence_question || raw.questionToAnswer || raw.question_to_answer || raw.question || raw.learningGoal || raw.learning_goal || '', 260);
|
||||||
const userValue = cleanText(raw.userValue || raw.user_value || raw.value || raw.outcome || raw.why, 260);
|
const userValue = cleanText(raw.userValue || raw.user_value || raw.value || raw.outcome || raw.why || raw.whyItMatters || raw.why_it_matters || raw.whyNow || raw.why_now, 260);
|
||||||
const risk = cleanText(raw.risk || raw.assumption || raw.unknown || '', 220);
|
const risk = cleanText(raw.risk || raw.assumption || raw.unknown || raw.watchOut || raw.watch_out || '', 220);
|
||||||
const nextStep = cleanText(raw.nextStep || raw.next_step || raw.nextAction || raw.next_action || raw.firstStep || raw.first_step || raw.manualStep || raw.manual_step || raw.actionToTake || raw.action_to_take || '', 260);
|
const nextStep = cleanText(raw.nextStep || raw.next_step || raw.nextAction || raw.next_action || raw.firstStep || raw.first_step || raw.manualStep || raw.manual_step || raw.actionToTake || raw.action_to_take || raw.firstProofStep || raw.first_proof_step || '', 260);
|
||||||
const successSignal = cleanText(raw.successSignal || raw.success_signal || raw.successCriteria || raw.success_criteria || raw.successMetric || raw.success_metric || raw.greenLight || raw.green_light || raw.signalToSee || raw.signal_to_see || '', 260);
|
const successSignal = cleanText(raw.successSignal || raw.success_signal || raw.successCriteria || raw.success_criteria || raw.successMetric || raw.success_metric || raw.greenLight || raw.green_light || raw.greenFlag || raw.green_flag || raw.signalToSee || raw.signal_to_see || '', 260);
|
||||||
const killSignal = cleanText(raw.killSignal || raw.kill_signal || raw.stopSignal || raw.stop_signal || raw.redFlag || raw.red_flag || raw.failureSignal || raw.failure_signal || raw.cutIf || raw.cut_if || '', 260);
|
const killSignal = cleanText(raw.killSignal || raw.kill_signal || raw.stopSignal || raw.stop_signal || raw.redFlag || raw.red_flag || raw.failureSignal || raw.failure_signal || raw.cutIf || raw.cut_if || raw.stopIf || raw.stop_if || '', 260);
|
||||||
const rawLane = cleanText(raw.lane || '', 40);
|
const rawLane = cleanText(raw.lane || '', 40);
|
||||||
const laneLooksLikeHint = Boolean(normalizeLaneHint(rawLane));
|
const laneLooksLikeHint = Boolean(normalizeLaneHint(rawLane));
|
||||||
const sourceSection = cleanText(raw.sourceSection || raw.source_section || raw.section || raw.origin || (!laneLooksLikeHint ? rawLane : '') || defaultSourceSection, 80);
|
const sourceSection = cleanText(raw.sourceSection || raw.source_section || raw.section || raw.origin || (!laneLooksLikeHint ? rawLane : '') || defaultSourceSection, 80);
|
||||||
@@ -1074,11 +1074,21 @@ function normalizeFeatureOption(item, index, fallbackId = 'feature', defaultSour
|
|||||||
proofSteps.length && `Proof steps: ${proofSteps.join('; ')}`,
|
proofSteps.length && `Proof steps: ${proofSteps.join('; ')}`,
|
||||||
dependencies.length && `Dependencies: ${dependencies.join(', ')}`,
|
dependencies.length && `Dependencies: ${dependencies.join(', ')}`,
|
||||||
].filter(Boolean);
|
].filter(Boolean);
|
||||||
|
const conflictText = cleanText([
|
||||||
|
title,
|
||||||
|
raw.description || raw.brief || (raw.hypothesis && raw.hypothesis !== title ? raw.hypothesis : ''),
|
||||||
|
userValue,
|
||||||
|
evidenceNeeded,
|
||||||
|
risk,
|
||||||
|
nextStep,
|
||||||
|
proofSteps.join(' '),
|
||||||
|
dependencies.join(' '),
|
||||||
|
].filter(Boolean).join(' '), 1200);
|
||||||
return {
|
return {
|
||||||
id: cleanText(raw.id || raw.key || `${fallbackId}-${index + 1}`, 80) || `${fallbackId}-${index + 1}`,
|
id: cleanText(raw.id || raw.key || `${fallbackId}-${index + 1}`, 80) || `${fallbackId}-${index + 1}`,
|
||||||
title,
|
title,
|
||||||
description: cleanText(descriptionParts.join(' '), 760),
|
description: cleanText(descriptionParts.join(' '), 760),
|
||||||
factors: { userValue, evidenceNeeded, risk, nextStep, successSignal, killSignal, proofSteps, dependencies, recommendedLane, metricHints: cleanMetricHints(raw) },
|
factors: { userValue, evidenceNeeded, risk, nextStep, successSignal, killSignal, proofSteps, dependencies, recommendedLane, metricHints: cleanMetricHints(raw), conflictText },
|
||||||
provenance: {
|
provenance: {
|
||||||
sourceId,
|
sourceId,
|
||||||
sourceSection,
|
sourceSection,
|
||||||
@@ -1558,7 +1568,7 @@ function scoreOption(option, mode, context = '', decisionContext = {}) {
|
|||||||
const dependencyPenalty = Math.min(2.2, (factors.dependencies || []).length * 0.45);
|
const dependencyPenalty = Math.min(2.2, (factors.dependencies || []).length * 0.45);
|
||||||
const laneHint = factors.recommendedLane || '';
|
const laneHint = factors.recommendedLane || '';
|
||||||
const normalizedLaneHint = normalizeLaneHint(laneHint);
|
const normalizedLaneHint = normalizeLaneHint(laneHint);
|
||||||
const conflicts = nonGoalConflicts(`${option.title} ${option.description}`, decisionContext);
|
const conflicts = nonGoalConflicts(factors.conflictText || `${option.title} ${option.description}`, decisionContext);
|
||||||
const nonGoalPenalty = Math.min(14, conflicts.length * 7);
|
const nonGoalPenalty = Math.min(14, conflicts.length * 7);
|
||||||
const laneBoost = /do|first|now|build/.test(laneHint) ? 3.1 : /validate|test|proof/.test(laneHint) ? 0.25 : /defer|park|cut/.test(laneHint) ? -0.75 : 0;
|
const laneBoost = /do|first|now|build/.test(laneHint) ? 3.1 : /validate|test|proof/.test(laneHint) ? 0.25 : /defer|park|cut/.test(laneHint) ? -0.75 : 0;
|
||||||
const lanePenalty = normalizedLaneHint === 'park' ? 18 : normalizedLaneHint === 'defer' ? 9 : 0;
|
const lanePenalty = normalizedLaneHint === 'park' ? 18 : normalizedLaneHint === 'defer' ? 9 : 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user