feat(radio): add output channel mapping (srcCh field)#7224
Draft
raphaelcoeffic wants to merge 109 commits intomodel-arenafrom
Draft
feat(radio): add output channel mapping (srcCh field)#7224raphaelcoeffic wants to merge 109 commits intomodel-arenafrom
raphaelcoeffic wants to merge 109 commits intomodel-arenafrom
Conversation
…ctions
Phase 1 of dynamic array allocation plan: introduce accessor functions
for all ModelData arrays (mixes, expos, curves, logical switches,
custom functions) and route all access through them. This prepares for
Phase 2 where the static arrays will be replaced by an arena allocator.
Key changes:
- Create expos.cpp/expos.h centralizing expo operations (insertExpo,
deleteExpo, copyExpo, moveExpo, getExpoCount) mirroring mixes.cpp
- Add curveHeaderAddress(), curvePointsBase(), customFnAddress() accessors
- Replace all direct g_model.{mixData,expoData,curves,points,logicalSw,
customFn}[] access with accessor function calls
- Remove duplicated expo insert/delete/copy code from colorlcd and
stdlcd GUI files
- Unify Lua API insertExpo to use centralized function across all targets
No data layout changes - all accessor functions still return pointers
into the existing static arrays. CHKSIZE checks and YAML output are
unchanged.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Introduce the arena allocator infrastructure and extend the YAML tree walker to support externally-stored arrays (YDT_EXTERN_ARRAY). ModelArena: manages a contiguous buffer where dynamic model data (mixes, expos, curves, etc.) will be stored. Supports section-based layout with insert/delete operations that shift subsequent sections. Arena backing is a static buffer sized per platform (4KB STM32F4, 8KB STM32H7, 64KB sim/companion), designed to be replaceable with heap or SDRAM in the future. YAML extern arrays: new YDT_EXTERN_ARRAY node type allows the tree walker to serialize/deserialize arrays stored outside the main struct. A get_ptr callback provides the base pointer and element count at runtime. The walker redirects data access via per-state data_override pointers, keeping existing YAML format fully compatible. No functional changes yet - all existing tests pass. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add yaml_get_mix_ptr, yaml_get_expo_ptr, yaml_get_curves_ptr, yaml_get_points_ptr, yaml_get_logical_sw_ptr, yaml_get_custom_fn_ptr to yaml_datastructs_funcs.cpp. These will be used as callbacks for YAML_EXTERN_ARRAY nodes once the struct migration is complete. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove mixData[], expoData[], curves[], points[], logicalSw[], and customFn[] from ModelData struct and store them in a separate arena managed by ModelArena. ModelData shrinks from ~6.5KB to ~2.5KB. Key changes: - ModelData now contains ModelDynData dyn (counts) instead of 6 arrays - All accessor functions (mixAddress, expoAddress, curveHeaderAddress, curvePointsBase, lswAddress, customFnAddress) redirect to arena - 22 generated YAML files updated: YAML_ARRAY -> YAML_EXTERN_ARRAY for the 6 arena-backed arrays, with get_ptr callbacks - CHKSIZE checks updated for all radio variants - Arena uses uint32_t for capacity/offsets (supports SDRAM arenas) - Arena auto-initialized with static constructor in edgetx.cpp - Tests updated to use accessor functions; MODEL_RESET clears arena Firmware compiles on colorlcd (X10), 212x64 (X9E), 128x64 (X7). All 98 unit tests pass. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Teach the YAML code generator (generate_yaml.py) to handle the new extern_array annotation type. When the generator encounters a CUST_EXTERN_ARRAY field in a struct, it: 1. Parses the tag name, max count, struct type, and get_ptr function from the annotation 2. Force-parses the referenced struct type (MixData, ExpoData, etc.) to compute the per-element bit size 3. Falls back to name-based parsing for fake structs (e.g. struct_signed_8) 4. Emits YAML_EXTERN_ARRAY(...) in the generated output via a new template clause All 21 radio targets regenerated with correct bit sizes. All 98 unit tests pass. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- RTC backup: include arena data in RamBackupUncompressed struct. rambackupWrite() copies arena to backup, rambackupRestore() copies it back and restores arena layout from g_model.dyn. - YAML read: clear arena before parsing model file. - YAML get_ptr callbacks: return MAX_* counts instead of current counts so the parser accepts up to the maximum number of elements during read. The writer's isElmtEmpty() naturally skips zero elements. All 98 tests pass. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ERN_ARRAY Pass C define names (MAX_MIXERS_HARD, etc.) through the YAML generator as strings rather than converting to integers. The generator emits them verbatim into the generated C++ code where the compiler resolves them. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Create customfn.h/customfn.cpp with insertCustomFn(), deleteCustomFn(), clearCustomFn() and move customFnAddress() there from edgetx.cpp. Replace direct memmove/memset operations on model custom functions in all 3 GUI variants (colorlcd, 212x64, 128x64) with centralized calls. Global (radio) custom functions keep the direct approach since they remain in RadioData's static arrays. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When readModelYaml() is called for a temporary model buffer (e.g., modelslist label operations), don't clear the arena - that would destroy the active model's arena data. Note: modelslist read-modify-write operations for non-active models still have a known issue where writeFileYaml() serializes the active model's arena arrays instead of the temp model's. This pre-dates the arena refactor (the old round-trip was already lossy for NOBACKUP fields). A proper fix requires text-based YAML modification or a second temp arena. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The label rename/move operations in modelslist.cpp do a full model round-trip (read YAML → modify header → write YAML) for non-current models. With the arena architecture, this corrupts the file because: - readModelYaml overwrites the active model's arena with the temp model - writeFileYaml serializes the (now wrong) arena data back This is a pre-existing design issue (the round-trip was already lossy for NOBACKUP fields). Needs replacing with direct YAML text manipulation. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the read-modify-write cycle in modelslist label operations with direct YAML text patching for non-current models. The old approach (readModelYaml → modify labels → writeFileYaml) was problematic because: 1. readModelYaml extern array callbacks overwrite the active model's arena with the temp model's data 2. writeFileYaml would then serialize the wrong arena data New approach: - patchModelYamlLabels(): reads YAML file as text, finds the "labels:" field, replaces its value, writes back. No model deserialization needed. - renameLabel(): builds new labels CSV from in-memory label map (no need to read the file for label data) - updateModelFile(): uses patchModelYamlLabels() for non-current models Also: readModelYaml() now saves/restores the arena when loading a temp model buffer, preventing arena corruption from extern array callbacks during any remaining temp reads (e.g. updateModelCell). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
23 new tests covering: - ModelArena class: attach, layout, clear, insertSlot, deleteSlot, full arena check, section offset cascading - Accessor functions: all 6 accessors point to arena memory, data round-trip through accessors, sections don't overlap - Insert/delete: insertMix, deleteMix, insertExpo preserve existing data; insertCustomFn/deleteCustomFn/clearCustomFn work correctly - Model reset: arena data is cleared Total: 121 tests (23 new + 98 existing), all passing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…el arrays Move the hard safety caps (MAX_MIXERS_HARD, MAX_EXPOS_HARD, etc.) from model_arena.h to dataconstants.h where they logically belong alongside the per-radio MAX_* constants. Size parallel state arrays to MAX_MIXERS_HARD: - mixState[MAX_MIXERS_HARD] (was MAX_MIXERS) - act[MAX_MIXERS_HARD] (was MAX_MIXERS) - activeMixes[MAX_MIXERS_HARD] (stack-local in mixer.cpp) This ensures the parallel arrays can accommodate the maximum number of mixes the arena could hold (128), not just the per-radio default (64). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Rewrite the plan document to reflect completed work (Phase 1 + 2) and detail the Phase 3 plan for dynamic arena sizing: - Populate ModelDynData counts after model load - Compact arena layout based on actual usage - Arena-aware insert/delete (insertInSection/deleteFromSection) - Dynamic loop bounds in mixer hot path - GUI memory indicator - STM32F4 arena sizing considerations - Known issues (modelslist temp reads, curveEnd sizing) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Document three categories of limits that constrain how far the hard caps can be raised: 1. Bit-field cross-references: struct fields that reference arena items by index (e.g. LimitData.curve:int8_t caps curves at 128, CurveRef.value:11 allows 2048, swtch:10 signed limits enum range) 2. Accessor function parameter types: all use uint8_t idx, capping at 255 elements. Sufficient for current hard caps (128) but must change to uint16_t if any array exceeds 255. 3. YAML infrastructure: YamlNode.elmts:12 allows 4095 elements, not a bottleneck. val_len uint8_t for parsing also fine. Includes per-array bottleneck summary and recommendations. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add detailed analysis of the MixSources enum range vs the 10-bit signed srcRaw field. On H7 radios with 99 telemetry sensors, the enum reaches 568 which overflows the 0-511 positive range — a pre-existing bug where sensors 81-99 can't be used as mix sources. Update bottleneck summary: mixes/expos can reach 255 (uint8_t idx), curves capped at 128 (LimitData.curve:int8_t), logical switches stuck at 64 (enum range pressure in 10-bit fields). Any increase to items in the MixSources enum requires widening srcRaw. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
5 new tests verify that enum ranges fit in their storage bit-fields: - SrcRawCanHoldAllMixSources: checks MIXSRC_LAST_TELEM <= 511 (srcRaw:10 signed). Will fail on H7 builds (99 sensors, enum=568) documenting the pre-existing overflow bug. - SrcRawRoundTrip: writes source values through actual MixData bitfield and verifies they survive. Detects corruption on overflow. - SwtchCanHoldAllSwitchSources: checks SWSRC_LAST <= 511 - CurveRefCanHoldAllCurves: checks MAX_CURVES_HARD <= 2047 - LimitCurveCanHoldAllCurves: checks MAX_CURVES_HARD <= 127 Total: 126 tests, all passing on X10 (60 sensors). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Document the long-term direction of replacing the monolithic MixSources/SwitchSources enums with structured type+index references. This removes the 10-bit enum range pressure that currently limits H7 radios to ~75 usable telemetry sensors as mix sources, and makes item counts independent of each other. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use 8-bit type + 8-bit flags + 16-bit index for source and switch references (32-bit word-aligned for ARM Cortex-M). The flags byte replaces the sign-bit inversion hack, and 16-bit index gives 65536 items per type. Note size impact: MixData grows ~10 bytes but this is offset by dynamic arena sizing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The srcRaw:10 overflow is a live bug on H7 and blocks raising any arena limits. Move structured source/switch references (SourceRef, SwitchRef) from Phase 5 to Phase 3b as top priority. Add detailed migration strategy, per-struct size impact analysis, and file inventory for the refactoring. Dynamic arena sizing moves to Phase 4. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Define 32-bit word-aligned structured reference types to replace the monolithic MixSources/SwitchSources enums: SourceRef: type(8) + flags(8) + index(16) - 22 source types covering all current MixSources categories - flags byte for inversion (replaces negative srcRaw encoding) - 16-bit index gives 65536 items per type SwitchRef: type(8) + flags(8) + index(16) - 12 switch types covering all current SwitchSources categories - flags byte for inversion (replaces negative swtch encoding) Conversion functions for gradual migration: - sourceRefFromMixSrc() / mixSrcFromSourceRef() - switchRefFromSwSrc() / swSrcFromSwitchRef() 22 unit tests verify round-trip conversion for all source/switch types including inputs, sticks, channels, telemetry, logical switches, flight modes, inverted sources, etc. 148 total tests, all passing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove sourceRefFromMixSrc/mixSrcFromSourceRef and switchRefFromSwSrc/ swSrcFromSwitchRef conversion functions. The old MixSources/SwitchSources enums and the new SourceRef/SwitchRef types should not coexist at runtime — the only backward compatibility interface is YAML. Add ValueOrSource struct (4 bytes, 32-bit aligned) to replace the 11-bit SourceNumVal union. Used for weight/offset/curve fields that hold either a numeric value or a full source reference. When isSource=1, the value field doubles as the source index and srcType identifies the source type. 17 struct-level tests (SourceRef, SwitchRef, ValueOrSource). 143 total tests, all passing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace bit-packed fields in MixData, ExpoData, and CurveRef with 32-bit structured types: - srcRaw:10 → SourceRef (4 bytes) - swtch:10 → SwitchRef (4 bytes) - weight:11 / offset:11 → ValueOrSource (4 bytes each) - CurveRef: type:5 + value:11 → type:8 + ValueOrSource (6 bytes) MixData: 20 → 35 bytes ExpoData: 18 → 33 bytes CurveRef: 2 → 6 bytes ~110 compile errors remain in call sites that use old integer-based access patterns. These need mechanical updating to use the new types. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Update mixer.cpp, mixes.cpp, expos.cpp, model_init.cpp, curves.cpp, edgetx.cpp for the new structured types: - mixer.cpp: add sourceRefToMixSrc()/switchRefToSwSrc() bridge functions, update getSourceNumFieldValue() to take ValueOrSource, update all srcRaw/swtch/weight/offset/curve.value accesses - mixes.cpp: SourceRef initialization, ValueOrSource.setNumeric() - expos.cpp: same pattern - model_init.cpp: same pattern - curves.cpp: ValueOrSource.numericValue() for curve dispatch, valueOrSourceToLegacy() bridge for string formatting - edgetx.cpp: type-based checks instead of enum range checks 75 errors remain in GUI and Lua code. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Update all GUI, Lua API, and test code for the new structured types. GUI (colorlcd): - input_edit, mixer_edit: explicit lambdas with bridge conversions for source/switch/weight/offset/curve editors - input_source: SourceRef↔mixsrc_t bridges for source picker - model_mixes, model_inputs: ValueOrSource for weight display, SourceRef for source display, SwitchRef for switch display - curve_param: ValueOrSource for curve value editing - getset_helpers.h: updated for new field types - gui_common: srcRaw.isNone() for channel usage checks Lua API (api_model.cpp): - mixSrcToSourceRef() / swSrcToSwitchRef() reverse bridges for SET path (Lua integers → structured types) - valueOrSourceToLuaInt() / luaIntToValueOrSource() replace old sourceNumValToLuaInt / luaIntToSourceNumval - All getMix/setMix/getInput/insertInput updated Tests: - mixer.cpp: all srcRaw/weight/swtch assignments updated - model_arena.cpp: arena tests use new types, bit-field capacity tests replaced with SourceRef/SwitchRef round-trip tests - lua.cpp: field assertions check .type/.index/.numericValue() Bridge functions (temporary, until all code uses SourceRef natively): - sourceRefToMixSrc(), switchRefToSwSrc() in mixer.cpp (non-static) - valueOrSourceToLegacy() in curves.cpp (non-static) Firmware compiles (X10), all 143 tests pass. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fix 128x64 and 212x64 input/mix editors and common stdlcd code: - model_input_edit, model_mix_edit (both LCD sizes): bridge conversions for srcRaw, swtch, weight, offset fields - model_curves, model_mixes, model_inputs, draw_functions: drawSource/drawSwitch/drawCurveRef use bridge functions - valueOrSourceToLegacy() moved outside COLORLCD guard All 3 targets build (X10, X9E, X7). 143 tests pass. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Rewrite DYNAMIC_ARRAYS_PLAN.md to reflect: - Phase 3b complete: SourceRef/SwitchRef/ValueOrSource in MixData, ExpoData, CurveRef (all 3 LCD targets build, 143 tests pass) - Updated arena element sizes (MixData 20→35, ExpoData 18→33) - STM32F4 arena undersize now critical (6208 > 4096) - Remaining structs to migrate (LogicalSwitchData, CustomFunctionData, FlightModeData, TimerData, SwashRingData, RadioData) - Bridge function consolidation needed - YAML serialization update needed for new types Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Convert all remaining 10-bit swtch/srcRaw fields to structured types: - LogicalSwitchData: v1/v3 widened to int16_t, andsw→SwitchRef - CustomFunctionData: swtch→SwitchRef, func→uint8_t - FlightModeData: swtch→SwitchRef - TimerData: swtch→SwitchRef - SwashRingData: all 3 source fields→SourceRef - ScriptDataInput: source→SourceRef - ModuleData.crsf: crsfArmingTrigger→SwitchRef - RadioData: backlightSrc/volumeSrc→SourceRef Bridge functions consolidated: mixSrcToSourceRef()/swSrcToSwitchRef() moved to mixer.cpp and declared in myeeprom.h. All static duplicates across GUI files removed. CFN_SWITCH/CFN_EMPTY macros updated for SwitchRef. All CHKSIZE values updated for all radio variants. All 3 LCD targets build (X10, X9E, X7). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Rewrite _getValue() to dispatch on SourceRef.type instead of MixSources enum ranges. getValue(SourceRef) handles inversion via ref.isInverted() instead of negative mixsrc_t encoding. Backward-compatible getValue(mixsrc_t) overload retained as a thin wrapper for callers that still use legacy enum values. Mixer hot path updated to pass SourceRef directly, eliminating sourceRefToMixSrc() roundtrips in applyExpos() and evalFlightModeMixes(). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> # Conflicts: # radio/src/mixer.cpp
Rewrite getSwitch() to dispatch on SwitchRef.type instead of SwitchSources enum ranges. Inversion handled via ref.isInverted() instead of negative swsrc_t encoding. Backward-compatible getSwitch(swsrc_t) inline overload retained for callers that still use legacy enum values. Mixer hot path and core engine (switches.cpp, mixer.cpp, timers.cpp, functions.cpp, edgetx.cpp) updated to pass SwitchRef directly, eliminating switchRefToSwSrc() roundtrips. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…expr SourceRef_(), SwitchRef_(), toUint32(), and fromUint32() were not constexpr, causing the Lua etxcst_entries LROT table to require runtime initialization (.data) instead of being a compile-time constant (.rodata). This added ~2.5 KB to the .data segment, overflowing FLASH on X9D+2019. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…class Move RadioData::customFn[64] from a fixed array to a separate g_radioArena, saving ~1 KB in RadioData for users who don't use global special functions. Refactor Arena class to be configured by a const ArenaDesc descriptor (numSections + elemSizes[]) instead of hardcoded 6-section arrays. All Arena methods are compiled once — no template monomorphisation. Model arena has 6 sections, radio arena has 1. - Add g_radioArena + radioArenaInit() - Add globalFnAddress/globalFnAllocAt/insertGlobalFn/deleteGlobalFn accessors mirroring the model custom function pattern - evalFunctions() takes explicit count parameter, removing the pointer-identity hack - Replace all g_eeGeneral.customFn[] direct access with accessors - layout() takes uint16_t counts[] — remove ModelDynData struct - Update RTC backup to save/restore both arenas via counts() Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
simuGetNumLogicalSwitches() and simuCopyLogicalSwitches() used MAX_LOGICAL_SWITCHES (64) instead of the actual arena section count. This caused the simulator to report/evaluate 64 logical switches even when the model only had a few allocated. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Eliminate the old GV_RANGE sentinel-range encoding (which depended on MAX_GVARS) from LimitData min/max/offset fields. Replace with a clean bit-15 flag scheme: bit 15 = 1 means gvar reference (index in bits 0-14), bit 15 = 0 means numeric value (15-bit signed in bits 0-14). Widen LimitData fields from 11-bit bitfields to full int16_t, ppmCenter from 10 to 12 bits (+2 bytes per LimitData, +64 bytes per model). Add GV_ENCODE/GV_DECODE helpers at all numeric read/write boundaries. Add GVarLimitTest suite (7 tests) covering encoding round-trips, gvar resolution through LIMIT_MAX/MIN/OFS, and end-to-end mixer clamping. Fix generate_yaml.py to use -std=c++14 and only count actual errors. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move FlightModeData[MAX_FLIGHT_MODES] and GVarData[MAX_GVARS] from fixed ModelData arrays into three new arena sections: ARENA_FLIGHT_MODES, ARENA_GVAR_DATA, and ARENA_GVAR_VALUES (placeholder for step 5b-2). - Bump ARENA_MAX_SECTIONS from 6 to 9 - flightModeAddress() now returns arena pointer - Add gvarDataAddress(), getFlightModeCount(), getGVarCount() accessors - Convert all ~39 files from g_model.flightModeData[i] / g_model.gvars[i] to arena accessor calls - Update RTC backup pack/unpack for new sections - Pre-allocate FM/GVar arena sections before YAML load and model init - Regenerate all yaml_datastructs_*.cpp Gvar values remain embedded in FlightModeData for now (extracted in 5b-2). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove gvar_t gvars[MAX_GVARS] from FlightModeData struct. Gvar values now live in a flat ARENA_GVAR_VALUES matrix indexed as base[fm * numGVars + gv]. GVAR_VALUE() becomes an inline function accessing the arena matrix. FlightModeData shrinks from ~46 to ~28 bytes (X9D+2019). Add gvarValues as a new CUST_EXTERN_ARRAY in ModelData for YAML serialization. Update fmd_is_active, model init, sdcard_yaml sentinel init, and RTC backup to use the flat matrix. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace MAX_FLIGHT_MODES/MAX_GVARS with getFlightModeCount()/getGVarCount() in loop bounds, GUI ranges, Lua bounds checks, and source availability. Keep compile-time MAX for: runtime state arrays (fp_act, lswFm), enum ranges (dataconstants.h), bitfields, template params, YAML hard limits, and switch loop initialization (lswFm needs full reset). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add a table to model_arena.h documenting the RAM cost outside the arena for each section type — the runtime state arrays (mixState, lswFm, fp_act, etc.) that scale with hard limits. This helps evaluate the cost of raising limits on constrained targets. Largest cost: lswFm = MAX_FLIGHT_MODES × MAX_LOGICAL_SWITCHES × 4 bytes. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace individual get_ptr/ensure_capacity function pointers in the _extern_array union member with a single const driver pointer. This shrinks the union member from 3 pointers to 2 while allowing future callbacks without growing YamlNode. Wire up gvar_is_active via the driver so gvarValues with default sentinel (GVAR_MAX+1) or zero are skipped during YAML output. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add getCurveCount(), getCurvePoints(), curveAllocAt() to properly allocate both ARENA_CURVES and ARENA_POINTS when creating curves - Bounds-check getCurvePoints(), loadCurves(), isCurveUsed() against arena section count to prevent reading adjacent section data - Fix colorlcd and B&W UI to use curveAllocAt() + storageDirty() when creating new curves - B&W UI: show only used curves + one empty slot instead of MAX_CURVES - Wire up fmd_is_active, cfn_is_active, isAlwaysActive for curves in ExternArrayDriver instances - Fix curveedit smooth toggle missing SET_DIRTY() - Add curve round-trip verification to YamlRoundTrip test Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Use indexed CUST_EXTERN_ARRAY for curves (matching main) so sparse curve slots preserve their index in YAML - Add curve_is_active callback using a bitmap populated by loadCurves() for O(1) checks instead of scanning all points on every save - Add Arena::trimSectionTo() for exact section count trimming - Add curveTrimTrailing() to reclaim both ARENA_CURVES and ARENA_POINTS when trailing curve slots are cleared - Color UI: call curveTrimTrailing() on curve clear - B&W UI: call curveTrimTrailing() on EXIT, setCurveUsed() on enter - Fix curveedit smooth toggle missing SET_DIRTY() - Add unit tests for trimSectionTo and curveTrimTrailing Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Cache section base pointers and loop counts before tight loops instead of calling arena accessors on every iteration. This avoids repeated indirection through g_modelArena._base + _offsets[] on each step. Loop over actual arena counts (getMixCount, getExpoCount, getLswCount) instead of MAX_* constants to skip empty slots. Remove redundant memset of static dummy in lswAddress. Cache lswAddress result in evalLogicalSwitches instead of calling it 3 times. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Avoid relying on static init ordering.
curveIsEmpty only checked the CurveHeader for zeros, but a default STANDARD curve with 5 points has an all-zero header. The UI marks curves via setCurveUsed(), so curveIsEmpty must also consult the flag bitmap to avoid trimming curves that have data. Fix tests to match UI behaviour: use lswAllocAt instead of lswAddress for out-of-range writes, and call setCurveUsed after curveAllocAt. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Arena::ensureSectionCapacity() can reallocate the buffer, but YamlTreeWalker held stale data_override pointers into the old buffer. Refresh data_override from get_ptr() after ensure_capacity in both toNextElmt() and the IDX handler. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Legacy GV6 format is 1-based; convert to 0-based index (GV6 → index 5) - Negative GVars (-GV6) now encode inversion via negative value in ValueOrSource (value = -(index+1)), decoded by toSourceRef() - setSource() preserves GVar inversion flag as negative value - New format !gv(5) round-trips correctly through setSource/toSourceRef Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
drawValueOrSource and editValueOrSource fell through to drawSource for GVar sources, which used getSourceString (! prefix for inversion). Use drawGVarName instead, matching main branch behavior (- prefix). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
dc04687 to
91a904e
Compare
Decouple mix channel outputs from RF outputs by adding a srcCh field to LimitData. Each output can now source from any mix channel (not just its own index), or be disabled entirely. Default value 0 preserves backward-compatible 1:1 mapping. - Add srcCh to LimitData struct and YAML schemas (all targets) - Mixer: ex_chans stays identity-mapped for mix chaining; only channelOutputs follows the mapping with per-output limits - Color LCD: Source field in output edit, mapped ex_chans for min/max highlighting, ComboChannelBar uses mapped source - B&W LCD (128x64, 212x64): Source field in output edit/list views - Lua: srcCh exposed in model.getOutput()/setOutput() - All reset handlers clear srcCh
91a904e to
f21b820
Compare
980e6fd to
bb85e4f
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Decouple mix channel outputs from RF outputs by adding a srcCh field to LimitData. Each output can now source from any mix channel (not just its own index), or be disabled entirely. Default value 0 preserves backward-compatible 1:1 mapping.