;APPLICATION
;- Name: MonitorMixer
;- Version: 1.0 Jan 20, 2016
;- Language: AutoIT v3.3.14.2
;- Author: Imurst
#cs
== BASIC USER HELP ==

DESCRIPTION
MonitorMixer finds mixed multi-monitor layouts. It caters to the complex, varied user requirements of PLP. Mixed-LLL is also supported. Layouts found will visually line up pixels across all screens, once positioned.

Layout matches are found via user-entered monitors &/or the monitor-database. This database is quite thorough & accurate enough; user can update it as they wish (it's not hard).
Detailed results are given in the program-window itself, which can be pasted to e.g. Word. File-dumps are available for global Match ALL reports & monitor lookup tables (for viewing in e.g. Notepad or Excel).
The program's mixed-layout search is thorough. User may therefore need to sift through many mixed-layout results, in order to find the layout best-suited to their needs.

INSTALLATION
- Portable, no install. Unzip it (it has accompanying files) to a folder, maybe called MonitorMixer. Make a link to it.
- Manditory files (included in zip): MonitorMixer.exe, Monitors.txt, MonitorMixer_SourceCode.txt.
- Dumped reports & tables will be saved to MonitorMixer's folder.

BASIC USAGE
1. User can optionally enter their own monitor specs, for a more personalized search. Input boxes include:
A. Viewable size: the actual, measured, diagonal size of your screen. Allows a number up to 11 decimals (which is stupid high; just be accurate). Can be in inches or cm.
B. Resolution X & Y: e.g. 1920 & 1080. Layout type won't change how these should be entered (i.e. enter PLP the same as LLL).

2. Each layout search type will result in a detailed report of all possible mixed matches, based on all user-options chosen.
A. Screen compare: Clicking Run will check if user-entered Center & Side Screen will work as a layout. If successful, the layout specs are displayed.
B. Find side: Clicking Run will find layouts that work with the user-entered center screen.
C. Find center: Clicking Run will find layouts that work with the user-entered side screen.
D. Match ALL: Clicking Run will search entire database of monitors, against itself. This finds all possible layouts, & dumps resulting reports to file (as it is likely very long). Two files are created:
- Layouts table: each row has 28 columns & includes all layout's specs required for accurate match-validation & reporting. Suitable for Excel. Sorted by layout-viewable-size, then by total-PPI.
- Layouts report: the verbose & formatted version of layouts-table. Designed for Notepad/Word. Layouts are listed & their specs described in readable form.
* Match ALL best-use:
- - Works best for PLP, for which it was created. Running it on mixed-LLL will result in the inclusion of tons of near-LLL layouts. Makes for a long boring report. But it works, & can be tweaked well with user's side-options.
- - Works best with user-option "Hang-by max" set quite high. This is because:
- - - bigger layouts look fine with longer hang,
- - - your huge-layout options increase dramatically.

3. Sift through the layout-results to find a layout that will work well for you.
- The report's descriptive text will help you choose, & disqualify inappropriate choices.
- For example, using a 4K side-monitor lowered to 1280x720 is not ideal, but it's a legit option; so it will show in layout-results, with a suitably negative description).

4. Further homework, once you have chosen a layout-result:
A. You can use SoftTH to see in picture-form what your layout will look like.
- Download SoftTH & use its graphical "SoftTh Config Tool.exe." Enter layout's Resolution specs (only). You can also show the Eyefinity H-shape version of a layout, by adjusting the Position input-box downward for Primary Head (i.e. center monitor).
- SoftTH will show you what layout will look like. Remember, all side-hang past center-monitor will be ignored by games.
B. Find monitor(s) online for purchase with the exact same specs.
- You may find it easiest to start this search at PLP Gaming wiki's Good Monitors pages, where monitors are listed by viewable size, & show brand-examples.
- Remember!
- - It is very important that "viewable size" (diagonal) be the same as shown in program's report. (Marketed "class" sizes can be quite inaccurate & cannot be trusted alone.)
- - If you can't find the viewable size for sale, back to the drawing board. (The database includes some hard-to-find sizes, & the rare one may not actually exist.)

OPTIONS
1. Layout type:
A. PLP: Calculations & validation are performed for a Portrait Landscape Portrait layout.
B. Mixed LLL: Calculations & validation are performed for a mixed-Landscape layout.

2. Units: inch/cm. Both user's monitor-input (for Viewable size & side-hang tweaks) & program's output will be in these units (unless specified. For example "PPI" is always inch-based).

3. Side Screen Tweaks:
A. Hang-by max:
Select or enter the maximum amount of extra physical material your sides are allowed to have vertically. I.e. Layout's sides can be #.# physically taller than center. Allows entry accuracy up to 8 decimals, & inch or cm.
Setting this too low will result in fewer search results, while increasing it will result in more. As layout-size increases, more side-hang looks acceptable.
If side-hang is too much, Eyefinity PLP is required to put half that hang on top.
But greater side-hang often increases overall side's width, making them fatter in-game. This is a great way to improve physically narrow-looking sides.
But setting max-hang too high (particularly on smaller layouts) will cause search-results to include ugly long-hanging sides. Very long-hanging sides are never ideal:
- Has 4 sizable mouse troughs to contend with (assuming Eyefinity PLP, where half the hang is stuck on top).
- Bit hard to mount with all that side-hang, unless you own a stand.
- Using a quite-short center with tall sides is a waste of central vision areas, low immersion.
- Works quite poorly in Manual PLP. Many gamers won't care, some will. Manual PLP requires the entire hang be on the bottom for proper gaming experience. So a massive hang quickly runs out of physical clearance, & requires reworking your layout-stand.
* The program's max-hang-check will include PPI variance in its calculation. I.e. the maximum side-hang allowed is a PERCEIVED amount, once layout is positioned. This allows program to accept the cases where a slightly-longer side with a certain PPI will NOT look longer, once positioned.
* When Match-ALL process is used, remember that max-hang is used as a constant while it searches through ALL layouts of all sizes. (Ideally max-hang allowable would slowly adjusted upward, as layouts grow in size; but it does not atm.)

B. Short-by max:
Select or enter the maximum amount that layout's sides can be physically shorter than center. It's a good option if you are in a pinch. Allows entry accuracy up to 8 decimals, & inch or cm.
But zero is the most appropriate value, always. Having sides shorter than center is not good, because gamer must decide one of two evils:
- Play game with its corners cropped off (since sides are too short). Not recommended unless it's a budget too-small overall layout.
- Play game with sides filled proper, but center is letterboxed (black margins top & bottom). This is pure evil, a waste of your center monitor & your central vision areas.
Increasing allowable side-shortness will result in more layout-results, but some will have bit-short sides.
* The program's shortness-check will include PPI variance in its calculation. I.e. the maximum side-shortness allowed is a PERCEIVED amount, once layout is positioned. This allows program to accept the cases where a slightly-short side with a certain PPI will NOT look short, once positioned.
* When Match ALL process is used, remember that it will use your max-shortness entry while it searches through ALL layouts of all sizes.

C. flexFit:
By default flexFit is True, which allows for a generally-suitable, reasonable amount of PPI variance in layout-search.
The amount of variance allowed is safe & good, being completely correctable via monitor positioning.
Some hide-a-bezel layouts will be included in search-results, but not very many (as they have higher PPI variance).
Setting flexFit to False will reduce search's allowable PPI variance. This will have the following effects:
- Far fewer overall layout-results, but the ones found will all be pretty close PPI matches.
- Monitors will either fit dead-flush or very near-flush, & will not need any special positioning to line up pixels.
- Zero chance of hide-a-bezel.

D. noGambler:
By default noGambler is True, which disables "gambler".
Setting this option to False will enable "gambler," which broadens PPI match-range.
This feature works with both flexFit True & False:
- If flexFit is False (i.e. user wants a tight monitor-match), then adding "gambler" will result in slightly less stringent PPI requirements (but still pretty tight).
- If flexFit is True (i.e. user wants a good match but it doesn't have to be tight), then adding "gambler" will result in REAL Gambler layouts being accepted in search-results.
REAL Gambler Layouts Described:
Useful option for either experienced or desparate users to seriously broaden PPI match-range.
PPI variance on gambler-layouts is pretty damn high, but still within reason for a gambler.
The goal of the serious gambling user is either:
- Experienced user: to find more hide-a-bezel layouts, particularly the ones that hide center's bezel (these are often gamblers). The experienced user will find these layouts with lower-chance for higher-awesome.
- Desparate user: to use more flexible PPI match-mechanics for user's problem monitor(s). An "it'll be good enough" mentality.
However, there is always a chance that REAL gamblers won't work out 100% ideal (regarding imprecise pixel line-up or un-ideal gap required for pixel line-up). Ideal outcome may depend on physical bezels' thickness.

E. allowAlt:
By default allowAltSide is True, which allows search-result to include side-monitors with lowered resolutions. This allows for many more potential layout-matches.
The resulting quality of lowering side's resolution is a bit reduced, being less crisp an image.
However, it still looks decent for work/surf & the quality-change is not noticed in game-session.
This is because gamer looks at center monitor, sides are primarily for peripheral vision. Side-quality therefore matters considerably less than center.
Setting this option to False will result in far fewer overall layout-results, but the ones found will all use stock/default side resolutions.

COMMAND BUTTONS
1. Run: activates layout-search, using all user-input & options chosen. The resulting layouts-report will display in program's window (unless Match ALL, which dumps to file).
2. Copy report: copies program's results-window content to your clipboard (e.g. for paste to Notepad).
3. Clear all: clears these input-widget groups: Center Screen, Side Screen, & results-window.

4. Spec tables:
Dumps to file the monitor spec-tables which comprise the program's database:
- Aspect table (3 columns)
- Stock monitors table (9 columns)
- Alt monitors table (9 columns)
These tables are generated within the program, from the simple core data in Monitors.txt. Latter is easily editable, described somewhere below.

3. Help: puts this text in program's report-window. (Text-source is found at the top of file MonitorMixer_SourceCode.txt)

== POWER-USER HELP ==

DATABASE (tables)
1. Monitors.txt:
This user-editable "table" is the source of all MonitorMixer's internal monitor-specs. It includes ResX, ResY & ViewableSize (in inches) per-monitor, basically from 17" up to 75" (there is one 15.43" included).
These monitors are grouped by same-resolution into sections.
A. Main sections: split by aspect, then numbered & titled e.g. ";1. 16:9, 1.78".
B. Sub-sections: split by resolution & titled e.g. "1.A.", "1.B." etc for all 16:9 resolution varieties. There are also two types of subsections:
- Monitors set at regular stock/default resolution.
- Monitors set at lowered resolution (i.e. "Alt" resolution). These alt-monitor sub-sections are titled e.g. ";1.E. ALT_1".
To update file's monitor specs:
- Close MonitorMixer & open the file (it's in <path-to-program>).
- Add a new line with your new monitor specs, pasting in where it logically would fit within the current monitors.
- Or remove monitor-rows you don't want to ever be searched, simply by deleting the line or prefix the line with a semicolon.
- Or edit existing monitor-row values, for example to make viewable-size more accurate.
- Rare more complex case: If your new monitor entry's resolution is different than all of the existing sections, you need to
- - Create a new resolution-section:
- - - Title the new section like this (resX,resY & you must use square brackets): [####,####]
- - - Add your new monitor-line beneath it.
- - Create new alt-resolution section(s), if they do not already exist:
- - - Jot down all the possible lowered-resolutions that this new monitor can handle.
- - - Look through the file's whole list of bracketed-lines & see which will need to be added (you want them ALL entered).
- - - If you find some lowered-resolution doesn't exist that should, create it:
- - - - Copy the new section (that you made), & paste it to make a new "alt section".
- - - - Fix this new section's title (to document what it is) & also its square-bracketed resolution (from stock-resolution to alt-resolution).
- Syntax:
- - The file's viewable-sizes must be entered in inches (not cm), which can have up to 11 decimals.
- - Semicolon prefix disables the whole line.
- - Blank lines are ignored.
- - Square-brackets are special. They denote which lines the program should use to create its Aspect table.

2. Dumpable Arrays:
- Aspect: includes ResX, ResY, AspectDec. Sorted by aspect descending. Inch units unless stated.
- Stock Monitors: includes ResX, ResY, ViewableDiag, AspectDec, ViewableWidth, ViewableHeight, PPI, OrigResX, OrigResY. The latter two elements are always zero here. Sorted by PPI descending. Inch units unless stated.
- Alt Monitors: includes same elements as Stock & sorted by PPI too (& uses inch units unless stated). But has two differences:
- - These are all monitors with lowered resolutions. None are stock.
- - OrigResX & Y stores the original stock ResX & Y for this alt-monitor. So ResX & Y are never zero here.
- Match ALL (layouts table): Sorted by layout-diagonal-inches, then by total-pixels, all ascending. Units are either inch or cm (chosen by user). Includes these elements:
- - Layout specs:
ResX, ResY, ViewableDiag, AspectDec, ViewableWidth, ViewableHeight, Ppi, PpiVariance, SID_AccumImg(vert_mm), TtlPxCount, TtlHang, sort
- - Center's specs:
CTR_ResX, CTR_ResY, CTR_ViewableDiag, CTR_Aspect, CTR_ViewableWidth, CTR_ViewableHeight, CTR_Ppi,
- - Side specs:
SID_ResX, SID_ResY, SID_ViewableDiag, SID_Aspect, SID_ViewableWidth, SID_ViewableHeight, SID_Ppi. Also OrigResX, OrigResY. The latter two elements store the original stock ResX & Y on an alt-monitor.

LAYOUT MATCHING MECHANICS (the main ones)
Program compares these key difference between center & side monitors:
1. Accumulated vertical image variance (let's call it "accum"):
It has been implied above that PPI variance is used for testing layouts. This is because no-one knows what "accum" is. But "accum" is actually being used, & not PPI itself.
A. Explained:
- Accum is the keystone of this program. It accurately brings a layout's pixel-lineup variance into real-world terms (in "millimeters of visible difference," positive or negative).
- It accounts for pixel-size's & pixel-total's physical-amplifying-affect on PPI-variance.
- It pinpoints how much more space the game-image will take up vertically, on side-screen VS center-screen, in any monitor scenario. Negative means side-image is shorter than center, & positive means side-image is longer.
- Accum is based partly on PPI. But PPI alone is not good enough for safe matching. PPI is an inaccurate matcher for many layouts, because "allowable" varies by monitor.
- Accum pseudocode (for inches): CtrResY / SidePpi - CtrHtInch.
- Since accum knows whether side-image is short or long, we can know whether to move the side-monitor forward or backward to acheieve perfect perceived pixel line-up. (Moving a monitor very-slightly toward/away from you strongly impacts side-screens' perceived PPI.)
- The amount that side-monitor must be moved is also trackable in an algorithm; though this latter is currently being done with a dictionary-lookup, based on real-world tests (because I didn't feel like making the algorithm, & the current dictionary-lookup works fine).
B. Settings Used: see _IsValidMatch() in source code.

2. Height (i.e. inches/cm):
- Sides are disqualified, for example, if they are either physically too short or aesthetically too tall.
- But the decision-making mechanics are more advanced: program instead uses PERCEIVED height variance. This is more valuable & accurate then a simple height-measurement.
- Perceived height variance is found by using "accum" in its calculation. This tells how much physical height variance will be perceived, once layout is positioned for perfect perceived pixel lineup.
- So for example if a slightly physically-short side-monitor is pulled slightly forward to lineup pixels with center, this side's perceived physical size grows to the eye. Once in proper position, this side could even be perceived as bit taller than center-monitor.
- So for program to be accurate & useful, it must be based on perceived height variance, & not actual measured height variance.

ACCURACY
1. This program is a sliding or fuzzy variety, taking non-ideal values & using them to pinpoint good layouts with fairly accurate specs. For this reason, not all layout specs will be dead-on (but close enough).
A. The layout-report prefixes all not-dead-on values with "~".
B. Main e.g.: No length-related value will be dead-on, but close enough. These include heightInch, widthInch, viewableSizeInch, hang-by-amount, PPI & "accum"... think that's it.
C. Input e.g.: The worst inaccuracy source for this program (which is ok) is the original monitor's viewable-size, via user-entered or Monitors.txt.
- This input & table depend entirely on hand-entered viewable-sizes. These sizes are not going to be dead-accurate (but are close enough).
- This means that all values calculated with "hand-entered" viewable-diagonal-values will be as inaccurate as the original value was. However, Monitors.txt is deemed accurate enough, & user just needs to measure proper.

2. Rounding.
This program always rounds internally to 13 digits. That is ridiculously accurate, when you consider inaccuracy from #1 above, & also how little difference it would make to a layout.
This internal accuracy is comfortable overkill, included for future program potential.
But when a difference-value is likely to be very-very-near-zero, AutoIT requires we round to 8 decimals (or it formats). Having to round like this happens rarely & has no negative impact.

KNOWN ISSUES / FUTURE IMPROVEMENT
1. Program is released with NO known issues. Program has not yet been thoroughly tested, but it appears accurate & is very useful. Developer will return eventually to test it more thoroughly.
2. Minor Oddities / Potential Improvements:
A. Match-ALL's logical side-hang.
- Max-side-hang (& max-side-short) user-option is a constant. But for a global-match-all, using max-side-hang as a constant is not logically ideal:
- - In this case, max-side-hang would ideally vary by center-screen size. This is because bigger layouts make a longish-hang look smaller & more acceptable.
- - FUTURE IMPROVEMENT?: For Match-ALL process, the hang-by-max should probably become a "class" of hang-by-max instead of a literal hang-by-max amount. Match-ALL's hang-by-max could then range higher as layout-size increases.
B. A layout's height is based on center-monitor's height, not sides. User should always use a full center, with sides tall enough for it.
But If a chosen layout's sides are bit shorter than center, program will still work well, with a minor inaccuracy:
Layout-height & layout-viewable-size are center-height-centric calculations & will be off a bit when sides are short (calcs assuming sides are long enough). Doesn't matter, it's close enough, & only affects output (not impacting actual layout-search).
C. One file-error-type will not get caught atm: if user can't save to disk. Program will just do nothing, but should rather give an error message. Would rarely happen, good enough. FUTURE IMPROVEMENT.
D. Mixed-LLL gets a lot more hits than PLP. This phenomenon is perhaps logical & appropriate, developer does not actually know. Mixed-LLL still needs to be tested proper. But the logic is sound, so mixed-LLL should be as safe as PLP.
3. This program's reports are slightly less verbose than PLP Gaming wiki's layout pages.
- Wiki flags a few more help-details to display. (But wiki is less populated with actual layouts, so program is far better overall.)
- FUTURE IMPROVEMENT?: More report-help-text could be added to this program, but it is very good as-is.
- Wiki help text that is NOT included in program:
- - Monitor type- & availability-related notes are not included in program-report (program has no access to this data, & likely will never be added. Way too annoying & data would get dated quickly).
- - Graphics card requirements lookup is not included in program. Developer decided it didn't fit here. A cleaner report, & gamer can figure card-requirements elsewhere. Plus extreme graphics cards are getting so strong now, makes it harder to describe what exactly is needed. The data gets dated.
- - Amount of hang is bit better described on wiki for small layouts, otherwise program-report is the same.
- - - Small layouts look worse with more hang & big layouts may look fine. So help-text ideally is based on layout-viewable-size. Program ignores this nuance atm. (Note to self: logic in colBJ, Func bones source bottom).
- - Center & side monitor shapes are described in text, but overall layout shape is not.
- - - Layout-shape is the least important to describe, as it has a clear feeling from surrounding report-specs. Layout shape could be coded, but low payoff, low priority. (Func bones at source's bottom.)
- - - Wiki vaguely describes overall layout-shape by grouping them into small/large etc. Program, however, displays layout-viewable-size (this is missing on wiki), & Match-ALL is simply one long list from smallest layouts to largest.

USER RIGHTS
- Freely offered code, change it how you want. If interest, there is room for general improvement (& cleanup), & there may be some other use for the code...
- Freely offered monitor specs, do anything you want. Specs have been researched meticulously, but there is room for improvement.
- If you improve either monitor specs file or program's code, please let developer know. Changes will then be checked & added to this original.
- This source code included in MonitorMixer's folder (MonitorMixer_SourceCode.txt).

DISCLAIMER
- Developer feels program output (with current specs database) is accurate enough to pinpoint good layouts. But developer takes zero responsibility if output is misleading in some way, causing user to either buy a layout that sucks, or miss a layout that's awesome.
- Bezel-3D-thickness is a minor but contributing factor to perceived pixel-lineup & related gaps (when significant PPI variance exists). It is therefore the user's responsibility to consider their own bezels while reading report's positioning instructions.
#ce
#include <WinAPIFiles.au3>
#include <GuiComboBox.au3>
#include <Array.au3>
#include <File.au3>
#include <String.au3>
#include <ButtonConstants.au3>
#include <EditConstants.au3>
#include <GUIConstantsEx.au3>
#include <StaticConstants.au3>
#include <WindowsConstants.au3>
Opt("GUIOnEventMode", 1)
Opt("GUIResizeMode", $GUI_DOCKAUTO)
#Region ###### START Koda GUI section ######
$frmMonitorMixer = GUICreate("MonitorMixer v1.0", 732, 545, @DesktopWidth/2 - 732/2, @DesktopHeight/2 - 545/2, BitOR($GUI_SS_DEFAULT_GUI,$WS_MAXIMIZEBOX,$WS_SIZEBOX,$WS_THICKFRAME,$WS_TABSTOP,$DS_MODALFRAME))
GUISetOnEvent($GUI_EVENT_CLOSE, "frmMonitorMixerClose")
GUISetOnEvent($GUI_EVENT_MINIMIZE, "frmMonitorMixerMinimize")
GUISetOnEvent($GUI_EVENT_MAXIMIZE, "frmMonitorMixerMaximize")
GUISetOnEvent($GUI_EVENT_RESTORE, "frmMonitorMixerRestore")
$grpProcessType = GUICtrlCreateGroup("Process Type", 7, 4, 103, 97)
GUICtrlSetResizing($grpProcessType, $GUI_DOCKLEFT+$GUI_DOCKTOP+$GUI_DOCKWIDTH+$GUI_DOCKHEIGHT)
$optSimpleCompare = GUICtrlCreateRadio("Screen compare", 12, 20, 94, 17)
GUICtrlSetState($optSimpleCompare, $GUI_CHECKED)
GUICtrlSetResizing($optSimpleCompare, $GUI_DOCKLEFT+$GUI_DOCKTOP+$GUI_DOCKWIDTH+$GUI_DOCKHEIGHT)
GUICtrlSetOnEvent($optSimpleCompare, "optSimpleCompareClick")
$optSidFind = GUICtrlCreateRadio("Find side", 12, 39, 65, 17)
GUICtrlSetResizing($optSidFind, $GUI_DOCKLEFT+$GUI_DOCKTOP+$GUI_DOCKWIDTH+$GUI_DOCKHEIGHT)
GUICtrlSetOnEvent($optSidFind, "optSidFindClick")
$optCtrFind = GUICtrlCreateRadio("Find center", 12, 58, 81, 17)
GUICtrlSetResizing($optCtrFind, $GUI_DOCKLEFT+$GUI_DOCKTOP+$GUI_DOCKWIDTH+$GUI_DOCKHEIGHT)
GUICtrlSetOnEvent($optCtrFind, "optCtrFindClick")
$optMatchAll = GUICtrlCreateRadio("Match ALL", 12, 77, 81, 17)
GUICtrlSetResizing($optMatchAll, $GUI_DOCKLEFT+$GUI_DOCKTOP+$GUI_DOCKWIDTH+$GUI_DOCKHEIGHT)
GUICtrlSetOnEvent($optMatchAll, "optMatchAllClick")
GUICtrlCreateGroup("", -99, -99, 1, 1)
$grpCtrScr = GUICtrlCreateGroup("Center Screen", 116, 4, 133, 97)
GUICtrlSetResizing($grpCtrScr, $GUI_DOCKLEFT+$GUI_DOCKTOP+$GUI_DOCKWIDTH+$GUI_DOCKHEIGHT)
$txtCtrSize = GUICtrlCreateInput("", 190, 21, 52, 21)
GUICtrlSetResizing($txtCtrSize, $GUI_DOCKLEFT+$GUI_DOCKTOP+$GUI_DOCKWIDTH+$GUI_DOCKHEIGHT)
$txtCtrResX = GUICtrlCreateInput("", 190, 47, 52, 21)
GUICtrlSetResizing($txtCtrResX, $GUI_DOCKLEFT+$GUI_DOCKTOP+$GUI_DOCKWIDTH+$GUI_DOCKHEIGHT)
$txtCtrResY = GUICtrlCreateInput("", 190, 73, 52, 21)
GUICtrlSetResizing($txtCtrResY, $GUI_DOCKLEFT+$GUI_DOCKTOP+$GUI_DOCKWIDTH+$GUI_DOCKHEIGHT)
$lblCtrSize = GUICtrlCreateLabel("Viewable size", 121, 25, 68, 17)
GUICtrlSetResizing($lblCtrSize, $GUI_DOCKLEFT+$GUI_DOCKTOP+$GUI_DOCKWIDTH+$GUI_DOCKHEIGHT)
$lblCtrResX = GUICtrlCreateLabel("Resolution X", 121, 51, 64, 17)
GUICtrlSetResizing($lblCtrResX, $GUI_DOCKLEFT+$GUI_DOCKTOP+$GUI_DOCKWIDTH+$GUI_DOCKHEIGHT)
$lblCtrResY = GUICtrlCreateLabel("Resolution Y", 121, 76, 64, 17)
GUICtrlSetResizing($lblCtrResY, $GUI_DOCKLEFT+$GUI_DOCKTOP+$GUI_DOCKWIDTH+$GUI_DOCKHEIGHT)
GUICtrlCreateGroup("", -99, -99, 1, 1)
$grpSidScr = GUICtrlCreateGroup("Side Screen", 255, 4, 133, 97)
GUICtrlSetResizing($grpSidScr, $GUI_DOCKLEFT+$GUI_DOCKTOP+$GUI_DOCKWIDTH+$GUI_DOCKHEIGHT)
$txtSidSize = GUICtrlCreateInput("", 329, 21, 52, 21)
GUICtrlSetResizing($txtSidSize, $GUI_DOCKLEFT+$GUI_DOCKTOP+$GUI_DOCKWIDTH+$GUI_DOCKHEIGHT)
$txtSidResX = GUICtrlCreateInput("", 329, 47, 52, 21)
GUICtrlSetResizing($txtSidResX, $GUI_DOCKLEFT+$GUI_DOCKTOP+$GUI_DOCKWIDTH+$GUI_DOCKHEIGHT)
$txtSidResY = GUICtrlCreateInput("", 329, 73, 52, 21)
GUICtrlSetResizing($txtSidResY, $GUI_DOCKLEFT+$GUI_DOCKTOP+$GUI_DOCKWIDTH+$GUI_DOCKHEIGHT)
$lblSidSize = GUICtrlCreateLabel("Viewable size", 260, 25, 68, 17)
GUICtrlSetResizing($lblSidSize, $GUI_DOCKLEFT+$GUI_DOCKTOP+$GUI_DOCKWIDTH+$GUI_DOCKHEIGHT)
$lblSidResX = GUICtrlCreateLabel("Resolution X", 260, 51, 64, 17)
GUICtrlSetResizing($lblSidResX, $GUI_DOCKLEFT+$GUI_DOCKTOP+$GUI_DOCKWIDTH+$GUI_DOCKHEIGHT)
$lblSidResY = GUICtrlCreateLabel("Resolution Y", 260, 76, 64, 17)
GUICtrlSetResizing($lblSidResY, $GUI_DOCKLEFT+$GUI_DOCKTOP+$GUI_DOCKWIDTH+$GUI_DOCKHEIGHT)
GUICtrlCreateGroup("", -99, -99, 1, 1)
$grpLayout = GUICtrlCreateGroup("Layout", 394, 4, 74, 53)
GUICtrlSetResizing($grpLayout, $GUI_DOCKLEFT+$GUI_DOCKTOP+$GUI_DOCKWIDTH+$GUI_DOCKHEIGHT)
$optPLP = GUICtrlCreateRadio("PLP", 399, 20, 49, 16)
GUICtrlSetState($optPLP, $GUI_CHECKED)
GUICtrlSetResizing($optPLP, $GUI_DOCKLEFT+$GUI_DOCKTOP+$GUI_DOCKWIDTH+$GUI_DOCKHEIGHT)
GUICtrlSetOnEvent($optPLP, "optPLPClick")
$optLLL = GUICtrlCreateRadio("Mixed LLL", 399, 37, 65, 16)
GUICtrlSetResizing($optLLL, $GUI_DOCKLEFT+$GUI_DOCKTOP+$GUI_DOCKWIDTH+$GUI_DOCKHEIGHT)
GUICtrlSetOnEvent($optLLL, "optLLLClick")
GUICtrlCreateGroup("", -99, -99, 1, 1)
$grpUnits = GUICtrlCreateGroup("", 394, 56, 74, 45)
GUICtrlSetResizing($grpUnits, $GUI_DOCKLEFT+$GUI_DOCKTOP+$GUI_DOCKWIDTH+$GUI_DOCKHEIGHT)
$optInch = GUICtrlCreateRadio("inch", 399, 65, 38, 16)
GUICtrlSetState($optInch, $GUI_CHECKED)
GUICtrlSetResizing($optInch, $GUI_DOCKLEFT+$GUI_DOCKTOP+$GUI_DOCKWIDTH+$GUI_DOCKHEIGHT)
GUICtrlSetOnEvent($optInch, "optInchClick")
$optCm = GUICtrlCreateRadio("cm", 399, 82, 32, 16)
GUICtrlSetResizing($optCm, $GUI_DOCKLEFT+$GUI_DOCKTOP+$GUI_DOCKWIDTH+$GUI_DOCKHEIGHT)
GUICtrlSetOnEvent($optCm, "optCmClick")
GUICtrlCreateGroup("", -99, -99, 1, 1)
$grpTweaks = GUICtrlCreateGroup("Side Screen Tweaks", 474, 4, 181, 97)
GUICtrlSetResizing($grpTweaks, $GUI_DOCKLEFT+$GUI_DOCKTOP+$GUI_DOCKWIDTH+$GUI_DOCKHEIGHT)
$lblMaxSideHang = GUICtrlCreateLabel("Hang-by max", 493, 26, 66, 17)
GUICtrlSetResizing($lblMaxSideHang, $GUI_DOCKLEFT+$GUI_DOCKTOP+$GUI_DOCKWIDTH+$GUI_DOCKHEIGHT)
$cboMaxSideHang = GUICtrlCreateCombo("cboMaxSideHang", 497, 45, 55, 25, BitOR($CBS_DROPDOWN,$CBS_AUTOHSCROLL))
GUICtrlSetResizing($cboMaxSideHang, $GUI_DOCKLEFT+$GUI_DOCKTOP+$GUI_DOCKWIDTH+$GUI_DOCKHEIGHT)
GUICtrlSetOnEvent($cboMaxSideHang, "cboMaxSideHangChange")
$lblMaxSideShort = GUICtrlCreateLabel("Short-by max", 572, 26, 65, 17)
GUICtrlSetResizing($lblMaxSideShort, $GUI_DOCKLEFT+$GUI_DOCKTOP+$GUI_DOCKWIDTH+$GUI_DOCKHEIGHT)
$cboMaxSideShort = GUICtrlCreateCombo("cboMaxSideShort", 576, 45, 55, 25, BitOR($CBS_DROPDOWN,$CBS_AUTOHSCROLL))
GUICtrlSetResizing($cboMaxSideShort, $GUI_DOCKLEFT+$GUI_DOCKTOP+$GUI_DOCKWIDTH+$GUI_DOCKHEIGHT)
GUICtrlSetOnEvent($cboMaxSideShort, "cboMaxSideShortChange")
$chkFlexFit = GUICtrlCreateCheckbox("flexFit", 479, 74, 44, 24)
GUICtrlSetState($chkFlexFit, $GUI_CHECKED)
GUICtrlSetResizing($chkFlexFit, $GUI_DOCKLEFT+$GUI_DOCKTOP+$GUI_DOCKWIDTH+$GUI_DOCKHEIGHT)
GUICtrlSetOnEvent($chkFlexFit, "chkFlexFitClick")
$chkNoGambler = GUICtrlCreateCheckbox("noGambler", 527, 74, 67, 24)
GUICtrlSetState($chkNoGambler, $GUI_CHECKED)
GUICtrlSetResizing($chkNoGambler, $GUI_DOCKLEFT+$GUI_DOCKTOP+$GUI_DOCKWIDTH+$GUI_DOCKHEIGHT)
GUICtrlSetOnEvent($chkNoGambler, "chkNoGamblerClick")
$chkAllowAltSide = GUICtrlCreateCheckbox("allowAlt", 599, 74, 52, 24)
GUICtrlSetState($chkAllowAltSide, $GUI_CHECKED)
GUICtrlSetResizing($chkAllowAltSide, $GUI_DOCKLEFT+$GUI_DOCKTOP+$GUI_DOCKWIDTH+$GUI_DOCKHEIGHT)
GUICtrlSetOnEvent($chkAllowAltSide, "chkAllowAltSideClick")
GUICtrlCreateGroup("", -99, -99, 1, 1)
$btnRun = GUICtrlCreateButton("Run", 660, 9, 65, 24)
GUICtrlSetResizing($btnRun, $GUI_DOCKLEFT+$GUI_DOCKTOP+$GUI_DOCKWIDTH+$GUI_DOCKHEIGHT)
GUICtrlSetOnEvent($btnRun, "btnRunClick")
$btnCopy = GUICtrlCreateButton("Copy report", 660, 32, 65, 18)
GUICtrlSetResizing($btnCopy, $GUI_DOCKLEFT+$GUI_DOCKTOP+$GUI_DOCKWIDTH+$GUI_DOCKHEIGHT)
GUICtrlSetOnEvent($btnCopy, "btnCopyClick")
$btnClearAll = GUICtrlCreateButton("Clear all", 660, 49, 65, 18)
GUICtrlSetResizing($btnClearAll, $GUI_DOCKLEFT+$GUI_DOCKTOP+$GUI_DOCKWIDTH+$GUI_DOCKHEIGHT)
GUICtrlSetOnEvent($btnClearAll, "btnClearAllClick")
$btnDumpSpecTables = GUICtrlCreateButton("Spec tables", 660, 66, 65, 18)
GUICtrlSetResizing($btnDumpSpecTables, $GUI_DOCKLEFT+$GUI_DOCKTOP+$GUI_DOCKWIDTH+$GUI_DOCKHEIGHT)
GUICtrlSetOnEvent($btnDumpSpecTables, "btnDumpSpecTablesClick")
$btnDumpHelp = GUICtrlCreateButton("Help", 660, 83, 65, 18)
GUICtrlSetResizing($btnDumpHelp, $GUI_DOCKLEFT+$GUI_DOCKTOP+$GUI_DOCKWIDTH+$GUI_DOCKHEIGHT)
GUICtrlSetOnEvent($btnDumpHelp, "btnDumpHelpClick")
$txtReport = GUICtrlCreateEdit("", 7, 107, 717, 430, BitOR($ES_AUTOVSCROLL,$ES_WANTRETURN,$WS_VSCROLL))
GUICtrlSetData($txtReport, "")
GUICtrlSetFont($txtReport, 10, 400, 0, "Arial")
GUICtrlSetResizing($txtReport, $GUI_DOCKAUTO+$GUI_DOCKLEFT+$GUI_DOCKRIGHT+$GUI_DOCKTOP+$GUI_DOCKBOTTOM)
GUICtrlSetOnEvent($txtReport, "txtReportChange")
GUISetState(@SW_SHOW)
#EndRegion ###### END Koda GUI section ######
#Region ###### Global Variables START ######
;CONSTANTS
   ;Process Type
Local Const $iPROC_ERR = 0
Local Const $iPROC_COMPARE = 1
Local Const $iPROC_FIND_SIDE = 2
Local Const $iPROC_FIND_CTR = 3
Local Const $iPROC_MATCH_ALL = 4
   ;table
	  ;cols (zero-based, totals are full ubound-type count)
Local Const $iASPECT_TABLE = 0
Local Const $iSTOCK_TABLE = 1
Local Const $iALT_TABLE = 2			;only used for dumping alt table to file.
Local Const $iLAYOUT_TABLE = 3		;only used for dumping Match ALL to file.
Local Const $iLAYOUT_REPORT = 4		;only used for dumping Match ALL to file.
Local Const $iRESX_COL = 0
Local Const $iRESY_COL = 1
Local Const $iINCH_DIAG_COL = 2		;diag-inch in monitor-tables (not in aspect table), & based on gaming-area in "layout"
Local Const $iASPECT_COLA = 2		;aspect col in aspect table
Local Const $iASPECT_COLB = 3		;aspect col in monitor-tables, & based on gaming-area in "layout"
Local Const $iINCH_WD_COL = 4
Local Const $iINCH_HT_COL = 5
Local Const $iDPI_COL = 6
Local Const $iRESXORI_COL = 7		;alt-monitors original resX, unless if stock-specs then 0. In a layout, this only relevant on sides; so that's what it stores.
Local Const $iRESYORI_COL = 8		;alt-monitors original resY, unless if stock-specs then 0. In a layout, this only relevant on sides; so that's what it stores.
	  ;layout-table also include...
Local Const $iDPI_VARIANCE_COL = 9	;dpi vs dpi
Local Const $iACCUM_IMG_COL = 10	;accumulated image variance. fuzzy accurate match-maker. better than dpi-matching (which is not safe, dpi doesn't account for the amplifying affects of px-count & px-size).
Local Const $iTTL_PX_CNT = 11		;total pixel count of gaming area
Local Const $iTTL_HANG_COL = 12		;side vs center height
		 ;layout-center
Local Const $iLAY_CTR_RESX_COL = 13
Local Const $iLAY_CTR_RESY_COL = 14
Local Const $iLAY_CTR_INCH_DIAG_COL = 15
Local Const $iLAY_CTR_ASPECT_COL = 16
Local Const $iLAY_CTR_INCH_WD_COL = 17
Local Const $iLAY_CTR_INCH_HT_COL = 18
Local Const $iLAY_CTR_DPI_COL = 19
		 ;layout-side
Local Const $iLAY_SID_RESX_COL = 20
Local Const $iLAY_SID_RESY_COL = 21
Local Const $iLAY_SID_INCH_DIAG_COL = 22
Local Const $iLAY_SID_ASPECT_COL = 23
Local Const $iLAY_SID_INCH_WD_COL = 24
Local Const $iLAY_SID_INCH_HT_COL = 25
Local Const $iLAY_SID_DPI_COL = 26
	  ;reporting sort aid. This would allow 2-col sort. would start life as 0.000# type float (eg secondary-sort, ttlPxCnt asc). Ends life with #.000# type float, allowing primary-sort (eg layoutDiagSize asc).
Local Const $iREP_SORT_COL = 27		;2-col sort aid, used for Match-ALL
	  ;totals
Local Const $iTTL_SPECS_CMN = 9		;common table specs total
Local Const $iTTL_SPECS_ASP = 3		;aspect table specs total
Local Const $iTTL_SPECS_LAY = 28	;layout (monitor-matched) total spec cols. layout + center + side + sort-aid.
   ;calc-type
	  ;rounding sucks in autoit, so here will workaround it. When autoit value is extremely close to zero, it stores a garbage number (it's formatted, which is completely inappropriate)
Local Const $iRND_PRECISE = 13		;used internally & in dumps. 15 decimals cause rare butchery, so we are dropping it to work. 14 still maybe not safe, would need to test careful.
Local Const $iRND_XL = 13			;for if PRECISE still may cause error. tested, it's fine leave it.
Local Const $iRND_LG = 9			;txtReport only atm
Local Const $iRND_NEARZERO = 8		;for when a difference will be very close to zero, need to round to fewer digits to keep AutoIT from formatting it.
Local Const $iRND_MD = 3
Local Const $iRND_MS = 2			;txtReport only atm
Local Const $iRND_SM = 1			;txtReport only atm
	   ;not used much
Local Const $iCALC_SPECS_CMN = 0	;required because aspect-table uses slightly different processing
Local Const $iCALC_SPECS_ASP = 1	;required because aspect-table uses slightly different processing
   ;strings
Local Const $sDir = @ScriptDir
Local Const $sCFG_NAME = "Monitors.txt"
Local Const $sSOURCECODE_NAME = "MonitorMixer_SourceCode.txt"
Local Const $aDUMP_FILENAME[5] = ["Dump_Aspect.txt", _
								 "Dump_Stock_Monitors.txt", _
								 "Dump_Alt_Monitors.txt", _
								 "Dump_Match_ALL_Table.txt", _
								 "Dump_Match_ALL_Report.txt"]
;VARIABLES (& data notes, remembering that var-type-names in many descriptions are not accurate, being rough for describing only)
   ;general
;Local $sScriptDir
Local $bDataInitialized = False				;init-check
Local $bDoFileOpen = True					;init-check
Local $iProcessType = $iPROC_ERR 			;0=fail, 1=simple compare, 2=sid find, 3=ctr find.
Local $bTester = False						;can use True to help test, e.g. creating "if True, show some half-baked stuff". some "=True" features will be left for quick tester use.
Local $sTester = ""							;can use as buffer of data to display in txtReport. requires additional statements to be added (where data is to be aquired).
   ;options
Local $bIsOptLayoutPLP = True
Local $bIsOptInch = True
Local $bIsOptFlexFit = True
Local $bIsOptNoGambler = True
Local $bIsOptAltSide = True
Local $fMaxSideHang = 0.0					;stored in inches or cm, depending on user option
Local $fMaxSideShort = 0.0					;stored in inches or cm, depending on user option
Local $fUnitCorrect = 1						;[one] inch or [$fUNIT_CM_DIFF]cm
Local $sUnitCorrect = Chr(34)				;for reporting
Local $fUNIT_CM_DIFF = 2.54
   ;arrays (& their flags. Where no flag, Monitor-arrays get flushed regular)
Local $aFileText[1]							;cfg file is split into this array
Local $bGetAspects = True					;init-flag
Local $aAspects[1][$iTTL_SPECS_ASP]			;[some]. not-wiped.
Local $aMyMonitors[2][$iTTL_SPECS_CMN] 		;[0=Ctr,1=Side]. wiped.
Local $aMyAltMonitors[1][$iTTL_SPECS_CMN]	;[few]. wiped.
Local $bGetStockMonitors = True				;init-flag
Local $aStockMonitors[1][$iTTL_SPECS_CMN]	;[many]. not-wiped.
Local $bGetAltMonitors = True				;init-flag
Local $aAltMonitors[1][$iTTL_SPECS_CMN]		;[tons]. not-wiped.
Local $aLayouts[1][$iTTL_SPECS_LAY]			;[some/tons]. wiped.

#EndRegion ###### Global Variables END ######
#Region ###### GUI-Related Logic START ######
;TESTER
   If $bTester = True then
		 ;NORMAL
	  GUICtrlSetData($txtCtrSize,27)
	  GUICtrlSetData($txtCtrResX,1920)
	  GUICtrlSetData($txtCtrResY,1080)
	  GUICtrlSetData($txtSidSize,19)
	  GUICtrlSetData($txtSidResX,1440)
	  GUICtrlSetData($txtSidResY,900)
		 ;FAKE ZERO-PPI-VARIANCE
	  ;GUICtrlSetData($txtCtrSize,27)
	  ;GUICtrlSetData($txtCtrResX,2560)
	  ;GUICtrlSetData($txtCtrResY,1440)
	  ;GUICtrlSetData($txtSidSize,15.60976408500055)
	  ;GUICtrlSetData($txtSidResX,1440)
	  ;GUICtrlSetData($txtSidResY,900)
   EndIf

RefreshCbos()

While 1
	Sleep(100)
WEnd

Func btnRunClick()
;start & manage common processing.
   Local $sReport
   Local $iMsgResponse

   GUICtrlSetData($txtReport,"")
   _ProcessWidgets()
   If $iProcessType = $iPROC_ERR Then Return		; bad user input, return user to correct it.
   If $bDoFileOpen = True Then _DoFileOpen()
   If $bGetAspects = True Then _GetFiledMonitors($aAspects, $iASPECT_TABLE)

   Select	;refesh "My" txtbox results always, & dont refresh general stock/alt/aspect tables
	  Case $iProcessType = $iPROC_COMPARE	;compare of txtbox monitors
		 _GetAltMonitors($aMyAltMonitors, $aMyMonitors)
		 $aLayouts = _FindLayouts($aMyMonitors, $aMyMonitors, $aMyAltMonitors)

	  Case $iProcessType = $iPROC_FIND_SIDE	;find side
		 If $bGetStockMonitors = True Then _GetFiledMonitors($aStockMonitors, $iSTOCK_TABLE)
		 If $bGetAltMonitors = True Then
			_GetAltMonitors($aAltMonitors, $aStockMonitors)
			$bGetAltMonitors = False
		 EndIf
		 $aLayouts = _FindLayouts($aMyMonitors, $aStockMonitors, $aAltMonitors)

	  Case $iProcessType = $iPROC_FIND_CTR	;find ctr
		 _GetAltMonitors($aMyAltMonitors, $aMyMonitors)
		 If $bGetStockMonitors = True Then _GetFiledMonitors($aStockMonitors, $iSTOCK_TABLE)
		 $aLayouts = _FindLayouts($aStockMonitors, $aMyMonitors, $aMyAltMonitors)

	  Case $iProcessType = $iPROC_MATCH_ALL
		 ;global match of all filed-monitors against all filed-monitors && alts-of-filed-monitors.
		 ;output array note:
		 ;- accounts for user preferences (gui: grpUnits, grpLayout, chkSidShort, chkSidLong)
		 ;- this dump outputs to an array that is not init-flagged.
		 ;- so will always redo search on each dump-run. to keep it, would require new global array.
		 ;- but shouldn't store because user could change prefs & re-run. also user unlikely to run this even twice in one sitting.
		 SplashTextOn("Preparing Dump","Layouts table being built, please wait.", 300, 100, -1, -1, 0, "", 10)
		 _BulkInitMonitors()

		 ;god-match, everything to everything++ \m/
		 $aLayouts = _FindLayouts($aStockMonitors, $aStockMonitors, $aAltMonitors)
		 _DumpTable($aLayouts, $iLAYOUT_TABLE)
		 _DumpReport()
		 SplashOff()
		 MsgBox ($MB_OK, "Dump Complete", Ubound($aLayouts) & " layouts dumped to " & $sDir & " folder." & @LF & _
				  "- Layouts Table:  " & $aDUMP_FILENAME[$iLAYOUT_TABLE] & @LF & _
				  "- Layouts Report:  " & $aDUMP_FILENAME[$iLAYOUT_REPORT] & @LF)
			   EndSelect

   If IsArray($aLayouts) Then
	  If $iProcessType <> $iPROC_MATCH_ALL Then
		 GUICtrlSetData($txtReport, Ubound($aLayouts) & $sTester & " LAYOUT(S) FOUND. See footer for options used." & @CRLF & @CRLF & _GetReport())
	  Else
		 GUICtrlSetData($txtReport, Ubound($aLayouts) & $sTester & " LAYOUT(S) FOUND. Dumped to file.")
	  EndIf
   Else
	  GUICtrlSetData($txtReport, $sTester & "No mixed layout found.")
   EndIf

;TESTER
   If $bTester = True then
	  ;_ArrayDisplay($aMyMonitors)
	  ;_ArrayDisplay($aAspects)
	  ;_ArrayDisplay($aStockMonitors)
	  ;_ArrayDisplay($aAltMonitors)
	  ;_ArrayDisplay($aMyAltMonitors)
	  ;If IsArray($aLayouts) Then
		 ;_ArrayDisplay($aLayouts)
	  ;Else
		 ;GUICtrlSetData($txtReport,GUICtrlRead($txtReport) & @CRLF & "$aLayouts=" & $aLayouts)
	  ;EndIf
	  GUICtrlSetData($txtReport, GUICtrlRead($txtReport) & $sTester)
   EndIf
EndFunc

Func btnCopyClick()
   ClipPut(GUICtrlRead($txtReport))
EndFunc

Func btnClearAllClick()
;adjust user interface
   	  GUICtrlSetData($txtCtrSize,"")
	  GUICtrlSetData($txtCtrResX,"")
	  GUICtrlSetData($txtCtrResY,"")
	  GUICtrlSetData($txtSidSize,"")
	  GUICtrlSetData($txtSidResX,"")
	  GUICtrlSetData($txtSidResY,"")
	  GUICtrlSetData($txtReport,"")
EndFunc

Func btnDumpSpecTablesClick()
   _BulkInitMonitors()
   _DumpTable($aAspects, $iASPECT_TABLE)
   _DumpTable($aStockMonitors, $iSTOCK_TABLE)
   _DumpTable($aAltMonitors, $iALT_TABLE)
   MsgBox ($MB_OK, "Dump Complete", "Monitor spec tables dumped to " & $sDir & " folder." & @LF & _
			"- Aspect Table:  " & $aDUMP_FILENAME[$iASPECT_TABLE] & @LF & _
			"- Stock Table:  " & $aDUMP_FILENAME[$iSTOCK_TABLE] & @LF & _
			"- Alt Table:  " & $aDUMP_FILENAME[$iALT_TABLE])
EndFunc

Func btnDumpHelpClick()
   GUICtrlSetData($txtReport, _GetHelp())
EndFunc

Func optSimpleCompareClick()
;adjust user interface
   If GUICtrlRead($optSimpleCompare) = $GUI_CHECKED Then
	  GUICtrlSetState($txtCtrSize,$GUI_ENABLE)
	  GUICtrlSetState($txtCtrResX,$GUI_ENABLE)
	  GUICtrlSetState($txtCtrResY,$GUI_ENABLE)
	  GUICtrlSetState($txtSidSize,$GUI_ENABLE)
	  GUICtrlSetState($txtSidResX,$GUI_ENABLE)
	  GUICtrlSetState($txtSidResY,$GUI_ENABLE)
   EndIf
EndFunc

Func optSidFindClick()
;adjust user interface
   If GUICtrlRead($optSidFind) = $GUI_CHECKED Then
	  GUICtrlSetData($txtSidSize,"")
	  GUICtrlSetData($txtSidResX,"")
	  GUICtrlSetData($txtSidResY,"")
	  GUICtrlSetState($txtCtrSize,$GUI_ENABLE)
	  GUICtrlSetState($txtCtrResX,$GUI_ENABLE)
	  GUICtrlSetState($txtCtrResY,$GUI_ENABLE)
	  GUICtrlSetState($txtSidSize,$GUI_DISABLE)
	  GUICtrlSetState($txtSidResX,$GUI_DISABLE)
	  GUICtrlSetState($txtSidResY,$GUI_DISABLE)
   EndIf
EndFunc

Func optCtrFindClick()
;adjust user interface
   If GUICtrlRead($optCtrFind) = $GUI_CHECKED Then
	  GUICtrlSetData($txtCtrSize,"")
	  GUICtrlSetData($txtCtrResX,"")
	  GUICtrlSetData($txtCtrResY,"")
	  GUICtrlSetState($txtCtrSize,$GUI_DISABLE)
	  GUICtrlSetState($txtCtrResX,$GUI_DISABLE)
	  GUICtrlSetState($txtCtrResY,$GUI_DISABLE)
	  GUICtrlSetState($txtSidSize,$GUI_ENABLE)
	  GUICtrlSetState($txtSidResX,$GUI_ENABLE)
	  GUICtrlSetState($txtSidResY,$GUI_ENABLE)
   EndIf
EndFunc

Func optMatchAllClick()
;adjust user interface
   GUICtrlSetData($txtCtrSize,"")
   GUICtrlSetData($txtCtrResX,"")
   GUICtrlSetData($txtCtrResY,"")
   GUICtrlSetData($txtSidSize,"")
   GUICtrlSetData($txtSidResX,"")
   GUICtrlSetData($txtSidResY,"")
   GUICtrlSetState($txtCtrSize,$GUI_DISABLE)
   GUICtrlSetState($txtCtrResX,$GUI_DISABLE)
   GUICtrlSetState($txtCtrResY,$GUI_DISABLE)
   GUICtrlSetState($txtSidSize,$GUI_DISABLE)
   GUICtrlSetState($txtSidResX,$GUI_DISABLE)
   GUICtrlSetState($txtSidResY,$GUI_DISABLE)
EndFunc

Func optPLPClick()
   $bIsOptLayoutPLP = True
EndFunc
Func optLLLClick()
   $bIsOptLayoutPLP = False
EndFunc
Func optInchClick()
   $bIsOptInch = True
   $fUnitCorrect = 1.0
   $sUnitCorrect = Chr(34)
   RefreshCbos()
EndFunc
Func optCmClick()
   $bIsOptInch = False
   $fUnitCorrect = $fUNIT_CM_DIFF
   $sUnitCorrect = "cm"
   RefreshCbos()
EndFunc
Func chkFlexFitClick()
   If GUICtrlRead($chkFlexFit) = $GUI_CHECKED Then
	  $bIsOptFlexFit = True
   Else
	  $bIsOptFlexFit = False
   EndIf
EndFunc
Func chkNoGamblerClick()
   If GUICtrlRead($chkNoGambler) = $GUI_CHECKED Then
	  $bIsOptNoGambler = True
   Else
	  $bIsOptNoGambler = False
   EndIf
EndFunc
Func chkAllowAltSideClick()
   If GUICtrlRead($chkAllowAltSide) = $GUI_CHECKED Then
	  $bIsOptAltSide = True
   Else
	  $bIsOptAltSide = False
   EndIf
EndFunc
Func chkNoSidShortClick()
   If GUICtrlRead($chkNoSidShort) = $GUI_CHECKED Then
	  $bIsOptNoSidShort = True
   Else
	  $bIsOptNoSidShort = False
   EndIf
EndFunc
Func chkNoSidTallClick()
  If GUICtrlRead($chkNoSidTall) = $GUI_CHECKED Then
	  $bIsOptNoSidTall = True
   Else
	  $bIsOptNoSidTall = False
   EndIf
EndFunc
Func frmMonitorMixerClose()
   Exit
EndFunc

Func RefreshCbos()
   Local Const $iTTL_TALLS = 16		;ie will be 0,1,2,3...15
   Local Const $iTTL_SHORTS = 11	;ie will be 0,0.1,0.2,0.3...1.0

   GUICtrlSendMsg ($cboMaxSideHang, $CB_RESETCONTENT, 0, 0 )
   GUICtrlSendMsg ($cboMaxSideShort, $CB_RESETCONTENT, 0, 0 )
	  ;fill cbos
   For $i = 0 To $iTTL_TALLS - 1
      GUICtrlSetData($cboMaxSideHang, $i * $fUnitCorrect)
   Next
   For $i = 0 To $iTTL_SHORTS - 1
      GUICtrlSetData($cboMaxSideShort, $i * 0.1 * $fUnitCorrect)
   Next

	  ;set to reasonable defaults
   _GUICtrlComboBox_SetCurSel($cboMaxSideHang, 4)
   _GUICtrlComboBox_SetCurSel($cboMaxSideShort, 1)
EndFunc

Func _ProcessWidgets()
;grab all user's entries
   Local $aInputBoxTxt[2] ;0=Center Screen,1=Side Screen. Comma-delimited string: ResX,Resy,diagInch
   Local $aScrSpecs
   Local Const $sERR_TITLE = "Bad Input"
   Local Const $sERR_MSG = "Fix your monitor input values."
	  ;Process Type
   Select
	  Case GUICtrlRead($optSimpleCompare) = $GUI_CHECKED
			;err-check blanks & preceding alpha (uncorrected type: alpha found elsewhere will crop to number preceding alpha-char-position)
		 If Number(GUICtrlRead($txtCtrResX)) = 0 Or _
			   Number(GUICtrlRead($txtCtrResY)) = 0 Or _
			   Number(GUICtrlRead($txtCtrSize)) = 0 Or _
			   Number(GUICtrlRead($txtSidResX)) = 0 Or _
			   Number(GUICtrlRead($txtSidResY)) = 0 Or _
			   Number(GUICtrlRead($txtSidSize)) = 0 Then
			MsgBox ($MB_OK, $sERR_TITLE, $sERR_MSG)
			$iProcessType = $iPROC_ERR
			Return
		 EndIf
		 $iProcessType = $iPROC_COMPARE

	  Case GUICtrlRead($optSidFind) = $GUI_CHECKED
		 If Number(GUICtrlRead($txtCtrResX)) = 0 Or _
			   Number(GUICtrlRead($txtCtrResY)) = 0 Or _
			   Number(GUICtrlRead($txtCtrSize)) = 0 Then
			MsgBox ($MB_OK, $sERR_TITLE, $sERR_MSG)
			$iProcessType = $iPROC_ERR
			Return
		 EndIf
		 $iProcessType = $iPROC_FIND_SIDE

	  Case GUICtrlRead($optCtrFind) = $GUI_CHECKED
		 If Number(GUICtrlRead($txtSidResX)) = 0 Or _
			   Number(GUICtrlRead($txtSidResY)) = 0 Or _
			   Number(GUICtrlRead($txtSidSize)) = 0 Then
			MsgBox ($MB_OK, $sERR_TITLE, $sERR_MSG)
			$iProcessType = $iPROC_FIND_CTR
			Return
		 EndIf
		 $iProcessType = $iPROC_FIND_CTR

	  Case GUICtrlRead($optMatchAll) = $GUI_CHECKED
		 $iProcessType = $iPROC_MATCH_ALL
   EndSelect

	  ;InputBoxes & cbos, Number() cleans input (strips any spaces or alpha, & allows 11 decimal-places. empty = 0). Abs makes sure no negative prefix is included.
	  ;- must store MyMonitors in inches to line up with other search-tables. will switch units only in Layouts table (for displaying).
	  ;- but user options (the two side-max-ht-value) are stored in either inch or cm, depending on user-options. These will be displayed.
   $aInputBoxTxt[0] = Abs(Number(GUICtrlRead($txtCtrResX))) &","& Abs(Number(GUICtrlRead($txtCtrResY))) &","& Abs(Number(GUICtrlRead($txtCtrSize)) / $fUnitCorrect)
   $aInputBoxTxt[1] = Abs(Number(GUICtrlRead($txtSidResX))) &","& Abs(Number(GUICtrlRead($txtSidResY))) &","& Abs(Number(GUICtrlRead($txtSidSize)) / $fUnitCorrect)
   $fMaxSideHang = Abs(Number(GUICtrlRead($cboMaxSideHang)))
   $fMaxSideShort = Abs(Number(GUICtrlRead($cboMaxSideShort)))
	  ;get remaining specs for 1 or both user monitors
   For $i = 0 To 1
	  $aScrSpecs = _CalcScreenSpecs($aInputBoxTxt[$i] & ", 0, 0")	;zeroes become col-content for ResXOrig & ResYOrig.
	  For $j = 0 To $iTTL_SPECS_CMN - 1
		 If StringLeft($aInputBoxTxt[$i],1) <> "0" Then
			$aMyMonitors[$i][$j] = Number($aScrSpecs[$j])
		 Else
			$aMyMonitors[$i][$j] = 0	;empty txtbox, user doesn't have this monitor.
		 EndIf
	  Next
   Next
EndFunc

#EndRegion ###### GUI-Related Logic END ######
#Region ###### Get More Monitors START ######

Func _GetFiledMonitors(ByRef $aMonitorBucket, $iTableType)
;params: (array to fill),(0=aspect table, 1=stock monitors table)
   Local $sCurrLine
   Local $iCurrLine = 0
   Local $iApproxCnt = Ubound($aFileText)
   Local $iCorrectedCnt = 0
   Local $iRemoveCnt = 0
   Local $aScrSpecs
   Local $iTtlCols = 0
   Local $iCalcType = 0

   If $iTableType = $iASPECT_TABLE Then
	  ReDim $aMonitorBucket[$iApproxCnt][$iTTL_SPECS_ASP]
	  $iTtlCols = $iTTL_SPECS_ASP
   Else 													;i.e. $iTableType = $iSTOCK_TABLE
	  ReDim $aMonitorBucket[$iApproxCnt][$iTTL_SPECS_CMN]
	  $iTtlCols = $iTTL_SPECS_CMN
   EndIf

   For $i = 0 To $iApproxCnt - 1
	  $sCurrLine = StringStripWS($aFileText[$i],$STR_STRIPALL)

	  If $iTableType = $iASPECT_TABLE Then
		 $iCalcType = $iCALC_SPECS_ASP
	  Else
		 $iCalcType = $iCALC_SPECS_CMN
	  EndIf
		 ;get remaining specs, bypassing non-monitor file-rows.
	  If _IsValidString($sCurrLine, $iTableType) Then
		 $aScrSpecs = _CalcScreenSpecs($sCurrLine&",0,0", $iCalcType)	;zeroes become col-content for ResXOrig & ResYOrig.
		 For $j = 0 To $iTtlCols - 1
			$aMonitorBucket[$iCorrectedCnt][$j] = Number($aScrSpecs[$j])
		 Next
		 $iCorrectedCnt += 1
	  EndIf
   Next

   If $iTableType = $iASPECT_TABLE Then
	  ReDim $aMonitorBucket[$iCorrectedCnt][$iTTL_SPECS_ASP]
	  _ArraySort($aMonitorBucket,1,0,0,$iASPECT_COLA)	;sort by aspect desc
	  $bGetAspects = False

   ElseIf $iTableType = $iSTOCK_TABLE Then 				;stock monitor table
	  ReDim $aMonitorBucket[$iCorrectedCnt][$iTTL_SPECS_CMN]
	  _ArraySort($aMonitorBucket,1,0,0,$iDPI_COL)		;sort by dpi desc
	  $bGetStockMonitors = False
   EndIf
EndFunc

Func _GetAltMonitors(ByRef $aAltMonitorBucket, ByRef $aSourceMonitors)
;get all filed-monitors' possible lowered resolution variations. params: AltMonitors, StockMonitors
   Local $iSourceStart = 0
   Local $iSourceCnt
   Local $iAspectCnt
   Local $iApproxBucketSize
   Local $aScrSpecs
   Local $iCorrectedCnt = 0

	  ;get source table dimensions. skip irrelevent my-monitor-rows.
   $iSourceCnt = Ubound($aSourceMonitors)	; = 2 for all "my-monitor" txtbox-grp variations, & = many for filed-monitors
	  ;find ctr or simple-compare, both requiring only sides for alt-sides (never for find-ctr, which always requires STOCK monitors (never alts).
   If $iProcessType = $iPROC_FIND_CTR Or $iProcessType = $iPROC_COMPARE Then $iSourceStart = 1
   $iAspectCnt = Ubound($aAspects)
   $iApproxBucketSize = $iAspectCnt
   ReDim $aAltMonitorBucket[$iApproxBucketSize][$iTTL_SPECS_CMN]

	  ;get aspects' alt resolutions to cobble alt-monitors
   For $i = $iSourceStart To $iSourceCnt - 1		;loop thru source table
	  For $j = 0 To $iAspectCnt - 1					;loop thru aspect table
		 If $aAspects[$j][$iASPECT_COLA] = $aSourceMonitors[$i][$iASPECT_COLB] Then	;same aspect? (join)
			If $aAspects[$j][$iRESX_COL] < $aSourceMonitors[$i][$iRESX_COL] Then	;lower res?
			   $aScrSpecs = _CalcScreenSpecs($aAspects[$j][$iRESX_COL] &","& $aAspects[$j][$iRESY_COL] &","& $aSourceMonitors[$i][$iINCH_DIAG_COL] &","& $aSourceMonitors[$i][$iRESX_COL] &","& $aSourceMonitors[$i][$iRESY_COL])
			   For $k = 0 To $iTTL_SPECS_CMN - 1									;store alt specs
				  $aAltMonitorBucket[$iCorrectedCnt][$k] = Number($aScrSpecs[$k])
			   Next
			   $iCorrectedCnt += 1
			EndIf
			If $iCorrectedCnt = $iApproxBucketSize - 1 Then
			   $iApproxBucketSize += Round(($iAspectCnt ^ 2) / 2)
			   ReDim $aAltMonitorBucket[$iApproxBucketSize][$iTTL_SPECS_CMN]
			EndIf
		 EndIf
	  Next
   Next

   ReDim $aAltMonitorBucket[$iCorrectedCnt][$iTTL_SPECS_CMN]
   _ArraySort($aAltMonitorBucket,1,0,0,$iDPI_COL)		;sort by dpi desc
EndFunc

#EndRegion ###### Get More Monitors END ######
#Region ###### Find Matches START ######

Func _FindLayouts(ByRef $aCenterSource, ByRef $aSideSource, ByRef $aAltSideSource)
;join monitor-arrays & find screen-matches.
;- one-to-many relation in processing usually, one ctr to many sides
;- except process-type3 requires many centers to a few sides, & dump-ALL relation is many centers to many sides.
;- so designed to allow looping through Centers, which makes many-to-many possible.
;params:
;- center, stock-/my-side, alt-side.
;- (Center is the "one" relation of each loop. re the "many" relation: stock-sides processed first, then alt-sides. then next Center.)
;output: multi-row 28-col array-of-matches. If zero matches, a zero integer is returned.
   Local $aLayoutBucket[1][$iTTL_SPECS_LAY]
   Local $fAccumImg
   Local $fHeightVariance
   Local $aScrSpecs
	  ;loop-management vars
   Local $iStartCenter = 0
   Local $iStartSide[2]	= [0,0]		;0=side,1=alt-side
   Local $iTtlCenters
   Local $aTtlSides[2]				;0=side,1=alt-side
   Local $aSideCntOffset[2] = [1,1]	;0=side,1=alt-side. basically used like an if-statement. workaround for when ttl-cnt needs reduction of last-elem-pos (due to MyMonitors storing zeroes on empty txtbox-monitor.
   Local $iApproxBucketSize
   Local $iCorrectedCnt = 0
   Local $bIsValidMatch = False
   Local $iTempTtl
   Local $iSideAltCol				;1 to allowAltSides, 0 to block all alt-sides. Processing same, except in actual find-loop where the alts will be skipped.
   Local Const $iSIDE_ALT_COL = 1
   Local Const $iSIDE_STD_COL = 0
   Local Const $fSECOND_SORT_VAL = 0.0001

   $iTempTtl = Ubound($aAspects)	;arbitrary count-starting value
   $iApproxBucketSize = $iTempTtl
	  ;search including alt-sides?
   If $bIsOptAltSide = True Then
	  $iSideAltCol = $iSIDE_ALT_COL
   Else
	  $iSideAltCol = $iSIDE_STD_COL
   EndIf

   ReDim $aLayoutBucket[$iApproxBucketSize][$iTTL_SPECS_LAY]
	  ;init for counting mech.
		 ;deciding how many loops & where they start.
   $aTtlSides[$iSIDE_ALT_COL] = Ubound($aAltSideSource)
		 ;(THIS CASE STATEMENT CAN BE SHRUNK, left fat for readability atm)
		 ;different processes, but all of them match center to side, by Stock-to-stock match && Stock-to-Alt match. (Also used for MyMonitor versions of these.)
   Select
	  Case $iProcessType = $iPROC_COMPARE
		 ;matches arg1[0](ctr) to arg2[1](sid) && to arg3[few](alt-sid)
			;re ctr
		 $iTtlCenters = 1
			;re sides
		 $iStartSide[$iSIDE_STD_COL] = 1		;starts loop at one, not zero
		 $aTtlSides[$iSIDE_STD_COL] = 1
		 $aSideCntOffset[$iSIDE_STD_COL] = 0	;loop starts late && goes to end (so not offset)

	  Case $iProcessType = $iPROC_FIND_SIDE
		 ;matches arg1[0](ctr) to arg2[many](sid) && to arg3[many](alt-sid)
			;re ctr
		 $iTtlCenters = 1
			;re sides
		 $aTtlSides[$iSIDE_STD_COL] = Ubound($aSideSource)

	  Case $iProcessType = $iPROC_FIND_CTR
		 ;matches arg1[many](ctr) to arg2[1](side) && to arg3[few](alt-side)
			;re ctr
		 $iTtlCenters = Ubound($aCenterSource)
			;re sides
		 $iStartSide[$iSIDE_STD_COL] = 1		;starts loop at one, not zero
		 $aTtlSides[$iSIDE_STD_COL] = 1
		 $aSideCntOffset[$iSIDE_STD_COL] = 0	;loop starts late && goes to end (so not offset)

	  Case $iProcessType = $iPROC_MATCH_ALL
		 ;matches arg1[many](ctr) to arg2[many](side) && to arg3[many](alt-side)
			;re ctr
		 $iTtlCenters = Ubound($aCenterSource)
			;re sides
		 $iStartSide[$iSIDE_STD_COL] = 0
		 $aTtlSides[$iSIDE_STD_COL] = Ubound($aSideSource)
	  EndSelect

   For $i = $iSIDE_STD_COL To $iSideAltCol	;first sides, then alt-sides
	  For $j = $iStartCenter To $iTtlCenters - 1
		 For $k = $iStartSide[$i] To $aTtlSides[$i] - $aSideCntOffset[$i]
			;accum_img is the keystone of this program. It is explained in fair detail under POWER-USER HELP near top.
			;accum_img pseudocode (used in program): [CtrResY / SidePpi - CtrHtInch].
			;which is equal to pseudocode (not used): [CtrPpi x CtrHtInchHt / SidePpi - CtrHtInch].
			;- IT'S STORED IN MM (inch source, but those units seem too large for human understanding of small values).
			;- IT CANNOT BE BASED ON WIDTH, as this would prove nothing useful (as it is an overall-px-ht-variance checker; that's what we care about).
			;- STORED DPI VALUE IS NOT EVER USED by itself for validation-decisions; it's only used for accum_img calc & reporting.
			;note to self, re accounting for pxSize's & pxTotal's affect on dpi-variance:
			;- PxSizeStandardized not needed nor used. Details:
			;- - old gimped faulty fuz, looked like this: (ctrDpi - sideDpi) x (1080 / inchHtCtr) / (pxHtCtr / inchHtCtr)
			;- - it's a historic eye-baller & far less accurate than current accumulated-image math. it was also plain dangerous on big sizes.
			;- - historically it was used to double-test layouts from 2 math-angles (gives a "feeling", & human-failure precaution). For this program, no double-test needed, as it's not human.
			   ;calc basic match dependencies:
				  ;init matcher-vars (std vs alt source)
			If $i = $iSIDE_STD_COL Then
				  ;ACCUM_IMG WARNING:
				  ;- PERMA MM-units (x$fUNIT_CM_DIFF, x10). To switch back to inch, just remove "$fUNIT_CM_DIFFx10x" (& edit validator function's accum min/max).
				  ;- rounded very-slightly, because otherwise (very rarely) number gets butched from an alpha suffix.
			   $fAccumImg = Round($fUNIT_CM_DIFF * 10 * (($aCenterSource[$j][$iRESY_COL] / $aSideSource[$k][$iDPI_COL]) - $aCenterSource[$j][$iINCH_HT_COL]), $iRND_NEARZERO)
			   If $bIsOptLayoutPLP = True Then		;is this PLP or LLL? PLP sides are portrait, htVariance & isValidMatch are sent sideW vs sideV.
					 ;htVariance minus accum shows side's-PERCEIVED-shortness, once layout positioned. This is not quite the same as an actual ht-measurement-variance, since we are including positional effects on vision.
						;ROUGH HIT EXAMPLES re storage & validation thinking
						;- if ++htVar And +accum		- very tall & bigger px. too much material, too long hang. sides must be moved backward. No borderline cases.
						;- if ++htVar And -accum		- very tall & small px. too much material, too long hang. sides must be moved forward. No borderline cases.
						;							- both these ++htVar situations must get blocked from results, by htVar-too-high.
						;- if +htVar And +accum		- tall & bigger px. likely enough material, side must be moved backward. There will be borderline cases here.
						;							- the htVar should be adjusted to include perception:
						;							- +htVar - +accum = perceived physical longness, once positioned.
						;- if 0htVar And 0accum		- perfect px & ht match, enough material.
						;							- 0htVar - 0accum = perceived physically same ht, once positioned flush.
						;- if -htVar And -accum		- short & smaller px, it is borderline case, must be moved forward.
						;							- -htVar - -accum = perceived physical shortness, once positioned.
						;- if -htVar And +accum		- short & bigger px, must be moved backward
						;							- -htVar - +accum = perceived too short, definitely.
						;- if +htVar And -accum		- long & smaller px. ht is likely fine, must be moved forward. There will be borderline cases here.
						;							- +htVar - -accum = perceived physical longness, once positioned.
				  $fHeightVariance = Round(($aSideSource[$k][$iINCH_WD_COL] * $fUnitCorrect) - ($aCenterSource[$j][$iINCH_HT_COL] * $fUnitCorrect) - (($fAccumImg / 10 / $fUNIT_CM_DIFF) * $fUnitCorrect), $iRND_NEARZERO)
			   Else	;mixed-LLL
						;see note above: htVariance minus accum...
					 $fHeightVariance = Round(($aSideSource[$k][$iINCH_HT_COL] * $fUnitCorrect) - ($aCenterSource[$j][$iINCH_HT_COL] * $fUnitCorrect) - (($fAccumImg / 10 / $fUNIT_CM_DIFF) * $fUnitCorrect), $iRND_NEARZERO)
			   EndIf
			Else
			   $fAccumImg = Round($fUNIT_CM_DIFF * 10 * (($aCenterSource[$j][$iRESY_COL] / $aAltSideSource[$k][$iDPI_COL]) - $aCenterSource[$j][$iINCH_HT_COL]), $iRND_NEARZERO)
			   If $bIsOptLayoutPLP = True Then		;is this PLP or LLL? PLP sides are portrait, htVariance & isValidMatch are sent sideW vs sideV.
					 ;see note above: htVariance minus accum...
				  $fHeightVariance = Round(($aAltSideSource[$k][$iINCH_WD_COL] * $fUnitCorrect) - ($aCenterSource[$j][$iINCH_HT_COL] * $fUnitCorrect) - (($fAccumImg / 10 / $fUNIT_CM_DIFF) * $fUnitCorrect), $iRND_NEARZERO)
			   Else
					 ;see note above: htVariance minus accum...
				  $fHeightVariance = Round(($aAltSideSource[$k][$iINCH_HT_COL] * $fUnitCorrect) - ($aCenterSource[$j][$iINCH_HT_COL] * $fUnitCorrect) - (($fAccumImg / 10 / $fUNIT_CM_DIFF) * $fUnitCorrect), $iRND_NEARZERO)
			   EndIf
			EndIf

			   ;Validate The Match. be careful adding any future checks. any to be added is safe here, but not safe up higher (in accum & htVar creation area). stear clear if at all possible.
				  ;Primary Check: perceived physical height & accum.
			$bIsValidMatch = _IsValidMatch($fAccumImg, $fHeightVariance)
				  ;Secondary Check Area:
					 ;ignore same-screen
						;mostly mixed-LLL req'd this "extension". it gets so many similar hits.
			If $i = $iSIDE_STD_COL Then
			   If $aCenterSource[$j][$iRESX_COL] = $aSideSource[$k][$iRESX_COL] And _
						$aCenterSource[$j][$iRESY_COL] = $aSideSource[$k][$iRESY_COL] And _
						$aCenterSource[$j][$iINCH_DIAG_COL] = $aSideSource[$k][$iINCH_DIAG_COL] Then
				  $bIsValidMatch = False
			   EndIf
			Else
			   If $aCenterSource[$j][$iRESX_COL] = $aAltSideSource[$k][$iRESX_COL] And _
						$aCenterSource[$j][$iRESY_COL] = $aAltSideSource[$k][$iRESY_COL] And _
						$aCenterSource[$j][$iINCH_DIAG_COL] = $aAltSideSource[$k][$iINCH_DIAG_COL] Then
				  $bIsValidMatch = False
			   EndIf
			EndIf

			If $bIsValidMatch = True Then
			   If $i = $iSIDE_STD_COL Then

				  $aScrSpecs = _CalcLayoutSpecs($aCenterSource, $aSideSource, $j, $k, $fAccumImg, $fHeightVariance)
;TESTER
				;  If $bTester = True Then
				;	 If $aScrSpecs[$iINCH_DIAG_COL] > 70 Then		;eg of finding some random problem-code case, to view related data in txtReport.
				;		$sTester = $sTester & @CRLF & "i:" & $i & ".STK. HeightVariance:" & ($aSideSource[$k][$iINCH_WD_COL] * $fUnitCorrect) & " - " & _
				;			  ($aCenterSource[$j][$iINCH_HT_COL] * $fUnitCorrect)  & " - " & _
				;			  (($fAccumImg / 10 / $fUNIT_CM_DIFF) * $fUnitCorrect) & " = " & _
				;			  Round(($aSideSource[$k][$iINCH_WD_COL] * $fUnitCorrect) - ($aCenterSource[$j][$iINCH_HT_COL] * $fUnitCorrect) - (($fAccumImg / 10 / $fUNIT_CM_DIFF) * $fUnitCorrect), $iRND_NEARZERO) & _
				;			  " (but it stores as " & $fHeightVariance & ")" & @CRLF
				;	 EndIf
				;  EndIf

			   Else
				  $aScrSpecs = _CalcLayoutSpecs($aCenterSource, $aAltSideSource, $j, $k, $fAccumImg, $fHeightVariance)
;TESTER
			;	  If $bTester = True Then
			;	  	If $aScrSpecs[$iINCH_DIAG_COL] > 70 Then		;eg of finding some random problem-code case, to view related data in txtReport.
			;		 	$sTester = $sTester & @CRLF & "i:" & $i & ".ALT. HeightVariance:" & ($aAltSideSource[$k][$iINCH_WD_COL] * $fUnitCorrect) & " - " & _
			;				  ($aCenterSource[$j][$iINCH_HT_COL] * $fUnitCorrect)  & " - " & _
			;				  (($fAccumImg / 10 / $fUNIT_CM_DIFF) * $fUnitCorrect) & " = " & _
			;				  Round(($aAltSideSource[$k][$iINCH_WD_COL] * $fUnitCorrect) - ($aCenterSource[$j][$iINCH_HT_COL] * $fUnitCorrect) - (($fAccumImg / 10 / $fUNIT_CM_DIFF) * $fUnitCorrect), $iRND_NEARZERO) & _
			;				  " (but it stores as " & $fHeightVariance & ")" & @CRLF
			;	  	EndIf
			;	  EndIf

			   EndIf

			   For $l = 0 To $iTTL_SPECS_LAY - 1
				  $aLayoutBucket[$iCorrectedCnt][$l] = Number($aScrSpecs[$l])
			   Next
			   $iCorrectedCnt += 1
			EndIf

			If $iCorrectedCnt = $iApproxBucketSize - 1 Then
			   $iApproxBucketSize += Round(($iTempTtl ^ 2) / 3)
			   ReDim $aLayoutBucket[$iApproxBucketSize][$iTTL_SPECS_LAY]
			EndIf
		 Next
	  Next
   Next

   If $iCorrectedCnt > 0 Then
	  ReDim $aLayoutBucket[$iCorrectedCnt][$iTTL_SPECS_LAY]
		 ;secondary sort asc
	  _ArraySort($aLayoutBucket,0,0,0,$iTTL_PX_CNT)
			;mark secondary sorts eg 0.0001
	  For $i = 0 To $iCorrectedCnt - 1
		 $aLayoutBucket[$i][$iREP_SORT_COL] = $i * $fSECOND_SORT_VAL
	  Next
		 ;primary sort asc
	  _ArraySort($aLayoutBucket,0,0,0,$iINCH_DIAG_COL)
			;mark primary sort, eg 1.0001
	  For $i = 0 To $iCorrectedCnt - 1
		 $aLayoutBucket[$i][$iREP_SORT_COL] = $aLayoutBucket[$i][$iREP_SORT_COL] + Floor($aLayoutBucket[$i][$iINCH_DIAG_COL])	;MAY WANT IT ROUNDED FURTHER TO GROUP SIMILAR (using floor atm, bit weak).
	  Next
		 ;primary & secondary sort, via marker, asc
	  _ArraySort($aLayoutBucket,0,0,0,$iREP_SORT_COL)

   Else				;turf the array, it's just an integer now (must check outside for flag of failure: if IsArray() ).
	  Local $aLayoutBucket = 0
   EndIf

   Return $aLayoutBucket
EndFunc

Func _IsValidMatch($fAccumImgMm, $fHeightVariance)
;params & internal variables:
;- resx, resy obvious.
;- accum:
;- - param is described where it's data is created, in _FindLayouts. Negative means move sides z-axis-forward, positive means move them z-axis-backward.
;- - accum-img-min/max variables must remain constant (changing only if Gambler-option-yes, to a more-flexible-constant), as it directly describes the unchanging acceptable-z-axis-range of side-screen placement.
;- height-variance:
;- - param data is currently the perceived-Side-wd minus perceived-Center-ht. A perceived-physical difference between center & side screens, once PLP layout is positioned.
   Local $bIsValidOverall = False
   Local Const $fGAMBLER = 1.75
   Local Const $fTALLSIDE_MAX_SM = 1.7
   Local Const $fTALLSIDE_MAX_LG = 3.7
	  ;the highest z-axis-toward-you we will allow (ie the biggest z-axis gap between screens [not re bezels, these will hug, varied depending on bezel thickness])
		 ;sides will fit in front of center
   Local $fAccumImgMin
	  ;the highest z-axis-away-from-you we will allow (ie again gap-related)
		 ;sides will fit behind center
   Local $fAccumImgMax
	  ;compare physical sizes against user side-height preferences.
		 ;check if sides will be perceived too short:
			;htVar minus accum shows side's-PERCEIVED-shortness, see note in FindLayouts. We are checking that stored skewed number against user-option requirements.
			;- this can also disqualify screens that are just long enough but have too much +accum (& would therefore have appeared short once positioned). Borderline stuff like that, this simple code will cover it due to the skewed storage value.
   If $fHeightVariance <= $fMaxSideHang And $fHeightVariance >= ($fMaxSideShort * -1) Then
		 ;set accum min/max based on user-choices. Amounts in mm, @HARDCODED from physical test results.
			;FLEXFIT
	  If $bIsOptFlexFit = True Then
			   ;-17mm (plus a hint) is my basic setting: "forward of center's bezel, hugging it fairly loosely & hiding one bezel (requires fair-sized but acceptable gap forward of center's bezel on medium-sized bezels. Less ideal on tiny bezels)."
		 $fAccumImgMin = -17.25
			   ;14mm (plus a hint) is my basic setting: "backward of center's bezel, hugging it fairly loosely & hiding one bezel (requires fair-sized but reasonable gap behind center's bezel on medium-sized bezels. Less ideal on tiny bezels)."
		 $fAccumImgMax = 14.25
	  Else		;be quite in-flexible
			   ;-6mm (plus a hint) is my basic setting: "slightly forward of center's bezel, still remaining bezel-to-bezel."
		 $fAccumImgMin = -6.25
			   ;4mm (plus a hint) is my basic setting: "slightly backward of center's bezel, still remaining bezel-to-bezel."
		 $fAccumImgMax = 4.25
	  EndIf
			;NOGAMBLER
			   ;adds to min/max, whether or not flexFit is True. This allows gambler-user to slightly bump up min/max, on either flexFit True or False. cool feature.
			   ;True by default (which does nothing extra in code)
	  If $bIsOptNoGambler = False Then
			   ;for flexFit-True users:
				  ;"forward of center's bezel, hugging it extremely loosely & hiding one bezel (requires unaccepable gap forward of center's bezel, unless using quite chunky bezels)."
				  ;ie -17.25 - 1.75 = -19
			   ;for flexFit-False users:
				  ;"noticeably forward of center's bezel, still remaining bezel-to-bezel."
				  ;ie = -6.25 - 1.75 = -8
		 $fAccumImgMin = $fAccumImgMin - $fGAMBLER
			   ;for flexFit-True users:
				  ;"backward of center's bezel, hugging it extremely loosely & hiding one bezel (requires unaccepable gap behind center's bezel, unless using quite chunky bezels)."
				  ;ie 14.25 + 1.75 = 16
			   ;for flexFit-False users:
				  ;"noticeably backward of center's bezel, still remaining bezel-to-bezel."
				  ;ie = 4.25 + 1.75 = 6
		 $fAccumImgMax = $fAccumImgMax + $fGAMBLER
	  EndIf

	  If $fAccumImgMm < $fAccumImgMax And $fAccumImgMm > $fAccumImgMin Then
		 $bIsValidOverall = True
	  EndIf
   EndIf

   Return $bIsValidOverall
EndFunc

#EndRegion ###### Find Matches END ######
#Region ###### Grunt Routines: Get Missing Table Specs START ######

Func _CalcScreenSpecs($sCurrMonitor, $iCalcType = $iCALC_SPECS_CMN)
;params:
;- sCurrMonitor is string (comma-delimited substrings):
;- - uncommon use: resX,resY,aspect. Called ~ aspect-table.
;- - common use:
;- - - 9-columns.
;- - - re the "ResOrig" sub-strings:
;- - - - it's the orig res, unless "stock-res" is same as "using-res", in which case it's passed & stores a zero.
;- - - - using zero here because:
;- - - - - mainly just makes all common-monitor-tables have same cols (seemed easier processing).
;- - - - - but also it creates a flag-implied (which will be useful): when zero, you known the original resolution is being used.
;- iCalcType:
;- - - 0=common: 9-spec fill.
;- - - 1=uncommon: for square-bracketed arg1 & it fills 3-spec aspect table.
;output:
;- aCurrMonitor is 9-column 2D array
;- see note re zeroes under "common use" above.
   Local $aCurrMonitor
   Local Const $TEMP_RESXORI_COL = 3	;col will be moved from here
   Local Const $TEMP_RESYORI_COL = 4	;col will be moved from here

   Select
	  Case $iCalcType = $iCALC_SPECS_CMN
			;grab resX,resY,diagInch
		 $aCurrMonitor = StringSplit($sCurrMonitor,",",$STR_NOCOUNT)
		 ReDim $aCurrMonitor[$iTTL_SPECS_CMN]
			;copy original resolution to proper col. Stored at array end, but must be processed before those cols are overwritten.
		 $aCurrMonitor[$iRESXORI_COL] = $aCurrMonitor[$TEMP_RESXORI_COL]
		 $aCurrMonitor[$iRESYORI_COL] = $aCurrMonitor[$TEMP_RESYORI_COL]
			;calc aspect (e.g. 1.77777...8)
		 $aCurrMonitor[$iASPECT_COLB] = Round($aCurrMonitor[$iRESX_COL] / $aCurrMonitor[$iRESY_COL], $iRND_PRECISE)
			;calc widthInch
		 $aCurrMonitor[$iINCH_WD_COL] = Round(Sqrt(($aCurrMonitor[$iINCH_DIAG_COL] ^2) * (($aCurrMonitor[$iRESX_COL] ^2) / (($aCurrMonitor[$iRESX_COL] ^2) + ($aCurrMonitor[$iRESY_COL] ^2)))), $iRND_PRECISE)
			;calc heightInch
		 $aCurrMonitor[$iINCH_HT_COL] = Round($aCurrMonitor[$iRESY_COL] / $aCurrMonitor[$iRESX_COL] * $aCurrMonitor[$iINCH_WD_COL], $iRND_PRECISE)
			;calc DPI
		 $aCurrMonitor[$iDPI_COL] = Round($aCurrMonitor[$iRESY_COL] / $aCurrMonitor[$iINCH_HT_COL], $iRND_XL)

	  Case $iCalcType = $iCALC_SPECS_ASP
			;grab resX,resY (after removing string's square-brackets)
		 $aCurrMonitor = StringSplit(StringTrimRight(StringTrimLeft($sCurrMonitor,1),1),",",$STR_NOCOUNT)
		 ReDim $aCurrMonitor[$iTTL_SPECS_ASP]
			;calc aspect (e.g. 1.77777...8)
		 $aCurrMonitor[$iASPECT_COLA] = Round($aCurrMonitor[$iRESX_COL] / $aCurrMonitor[$iRESY_COL], $iRND_PRECISE)
   EndSelect

   Return $aCurrMonitor
EndFunc

Func _CalcLayoutSpecs(ByRef $aCenterSource, ByRef $aSideSource, $iKeyCenter, $iKeySide, $fAccumImg, $fHeightVariance)
;params:
;- 1 & 2: ctr&side monitors, ByRef to massage Key-row entries into one layout-array-row. (could have been two 9-col 1-row arrays, but didn't for simplicity (maybe bad choice dunno it works).
;- 3 & 4: keys, the element-row where matched-monitor is from (allows retrieval of stock specs for report)
;- 5 & 6: layout values that are needed & calc'd previously, before this func is called. Passing them here reduces code duplication.
;returns
;- 1-row 28-col array: a layout.
;- - common cols are reused for "layout" [0-8]
;- - the remaining cols are additional layout specs, which are just center- & side-specs
   Local $aCurrLayout[$iTTL_SPECS_LAY]
   Local Const $iColOffsetCenter = $iLAY_CTR_RESX_COL	;13
   Local Const $iColOffsetSide = $iLAY_SID_RESX_COL		;20
	  ;0=ResX (actual)
   If $bIsOptLayoutPLP = True Then		;is this PLP or LLL?
	  $aCurrLayout[$iRESX_COL] = $aCenterSource[$iKeyCenter][$iRESX_COL] + ($aSideSource[$iKeySide][$iRESY_COL] * 2)
   Else
	  $aCurrLayout[$iRESX_COL] = $aCenterSource[$iKeyCenter][$iRESX_COL] + ($aSideSource[$iKeySide][$iRESX_COL] * 2)
   EndIf
	  ;1=ResY (actual on center, perceived on sides. ATM...) see notes under 5=heightInch, THIS CODE MAY CHANGE IN FUTURE to account for further options.
   $aCurrLayout[$iRESY_COL] = $aCenterSource[$iKeyCenter][$iRESY_COL]
	  ;#2 is lower down.
	  ;3=aspect: see notes under 5=heightInch, THIS CODE MAY CHANGE IN FUTURE to account for further options.
   $aCurrLayout[$iASPECT_COLB] = Round($aCurrLayout[$iRESX_COL] / $aCurrLayout[$iRESY_COL], $iRND_PRECISE)	;after knowing pxHt & pxWd of game-area
	  ;4=widthInch (actual)
   If $bIsOptLayoutPLP = True Then		;is this PLP or LLL?
	  $aCurrLayout[$iINCH_WD_COL] = Round(($aCenterSource[$iKeyCenter][$iINCH_WD_COL] * $fUnitCorrect) + (($aSideSource[$iKeySide][$iINCH_HT_COL] * $fUnitCorrect) * 2), $iRND_PRECISE)
   Else
	  $aCurrLayout[$iINCH_WD_COL] = Round(($aCenterSource[$iKeyCenter][$iINCH_WD_COL] * $fUnitCorrect) + (($aSideSource[$iKeySide][$iINCH_WD_COL] * $fUnitCorrect) * 2), $iRND_PRECISE)
   EndIf
	  ;5=heightInch (actual on center, perceived on sides. ATM....)
		 ;see important note under "KNOWN ISSUES / FUTURE IMPROVEMENT #2B": A layout's height is based on center-monitor's height, not sides.
		 ;THIS CODE MAY CHANGE SOME, for more accurate short-side report-values (just layout-ht & layout-viewable-size). But doubtful, because: there is no good way to code for this weirdness to produce meaningful output, there is minimal benefit to this improvement & short-sides are not normal (budget niche).
   $aCurrLayout[$iINCH_HT_COL] = $aCenterSource[$iKeyCenter][$iINCH_HT_COL] * $fUnitCorrect
   	  ;2=diagInch, after knowing htInch & wdInch gaming-area
   $aCurrLayout[$iINCH_DIAG_COL] = Round(Sqrt(($aCurrLayout[$iINCH_HT_COL] ^ 2) + ($aCurrLayout[$iINCH_WD_COL] ^ 2)), $iRND_PRECISE)
	  ;6=dpi (perceived)
   $aCurrLayout[$iDPI_COL] = $aCenterSource[$iKeyCenter][$iDPI_COL]	;(= the center's PPI; sides will be perceived as this once positioned to line-up w ctr).
	  ;7=RexXOrig,
   $aCurrLayout[$iRESXORI_COL] = $aSideSource[$iKeySide][$iRESXORI_COL]
	  ;8=ResYOrig,
   $aCurrLayout[$iRESYORI_COL] = $aSideSource[$iKeySide][$iRESYORI_COL]
	  ;9=dpi-variance,
   $aCurrLayout[$iDPI_VARIANCE_COL] = Round($aCenterSource[$iKeyCenter][$iDPI_COL] - $aSideSource[$iKeySide][$iDPI_COL], $iRND_NEARZERO)
	  ;10=accumulated-image (fuzzy)
   $aCurrLayout[$iACCUM_IMG_COL] = $fAccumImg
	  ;11=total-px-count.
   $aCurrLayout[$iTTL_PX_CNT] = Round($aCurrLayout[$iRESX_COL] * $aCurrLayout[$iRESY_COL], $iRND_PRECISE)	;after knowing pxWd gaming-area
	  ;12=total-hang (likely will use actual & not perceived, which is fuzzily more accurate)
   $aCurrLayout[$iTTL_HANG_COL] = $fHeightVariance
	 ;append center's specs for report
   For $i = $iLAY_CTR_RESX_COL To $iLAY_CTR_DPI_COL
	  If $i = $iLAY_CTR_INCH_DIAG_COL Or $i = $iLAY_CTR_INCH_WD_COL Or $i = $iLAY_CTR_INCH_HT_COL Then
		 $aCurrLayout[$i] = $aCenterSource[$iKeyCenter][$i - $iColOffsetCenter] * $fUnitCorrect
	  Else
		 $aCurrLayout[$i] = $aCenterSource[$iKeyCenter][$i - $iColOffsetCenter]
	  EndIf
   Next
	 ;append side's specs
   For $i = $iLAY_SID_RESX_COL To $iLAY_SID_DPI_COL
	  If $i = $iLAY_SID_INCH_DIAG_COL Or $i = $iLAY_SID_INCH_WD_COL Or $i = $iLAY_SID_INCH_HT_COL Then
		 $aCurrLayout[$i] = $aSideSource[$iKeySide][$i - $iColOffsetSide] * $fUnitCorrect
	  Else
		 $aCurrLayout[$i] = $aSideSource[$iKeySide][$i - $iColOffsetSide]
	  EndIf
  Next

   Return $aCurrLayout
EndFunc

#EndRegion ###### Grunt Routines: Get Missing Table Specs END ######
#Region ###### Grunt Routines: Read & Write START ######

Func _BulkInitMonitors()
   If $bDoFileOpen = True Then _DoFileOpen()
   If $bGetAspects = True Then _GetFiledMonitors($aAspects, $iASPECT_TABLE)
   If $bGetStockMonitors = True Then _GetFiledMonitors($aStockMonitors, $iSTOCK_TABLE)
   If $bGetAltMonitors = True Then
	  _GetAltMonitors($aAltMonitors, $aStockMonitors)
	  $bGetAltMonitors = False
   EndIf
EndFunc

Func _IsValidString($s, $iTableType)
;params: (string to validate),(0=aspect table str, 1=stock monitors table str). no other use.
   If StringLen($s) = 0 Or _
		 StringLeft($s,1) = ";" Or _
		(StringLeft($s,1) = "[" And $iTableType <> $iASPECT_TABLE) Or _
	    (StringLeft($s,1) <> "[" And $iTableType = $iASPECT_TABLE) Then
	  Return False
   EndIf
   Return True
EndFunc

Func _DoFileOpen()
;read file into array for processing.
   Local $sFileOpen
   Local $sFileText
   $sFileOpen = FileOpen($sCFG_NAME)
   If $sFileOpen = -1 Then
	  MsgBox ($MB_OK, "Missing File", "Monitor specs-source not found: " & @CR & $sCFG_NAME)
	  Return
   EndIf

   $sFileText = FileRead($sFileOpen)
   $aFileText = StringSplit($sFileText,@CRLF,$STR_ENTIRESPLIT + $STR_NOCOUNT)
   FileClose($sFileOpen)
   $bDoFileOpen = False
EndFunc

Func _GetReport()
   ;Local $sTitle = "" 		prefixed outside at call instead (title varies bit)
   Local $sTempBody = ""
   Local $sBody	= ""		;eg "Line1" & @CRLF & "Line2"
   Local $sFoot = ""
   Local Const $sSeparator = "-----------------------------------------------------------------"

   If IsArray($aLayouts) = False Then
	  GUICtrlSetData($txtReport,"No mixed layout found.")
	  Return
   EndIf
	  ;body
   For $i = 0 To Ubound($aLayouts) - 1
		 ;Layout
	  $sTempBody = $sTempBody & "LAYOUT " & ($i + 1) & @CRLF
			;eg ##.#" 17-23-17 (1920x1200 & 1280x1024)
	  $sTempBody = $sTempBody & @TAB & Round($aLayouts[$i][$iINCH_DIAG_COL],$iRND_SM) & $sUnitCorrect & " " & _
			   Round($aLayouts[$i][$iLAY_SID_INCH_DIAG_COL], $iRND_SM) & "-" & Round($aLayouts[$i][$iLAY_CTR_INCH_DIAG_COL], $iRND_SM) & "-" & Round($aLayouts[$i][$iLAY_SID_INCH_DIAG_COL], $iRND_SM) & " (" & _
			   $aLayouts[$i][$iLAY_CTR_RESX_COL] & "x" & $aLayouts[$i][$iLAY_CTR_RESY_COL] & " & " & _
			   $aLayouts[$i][$iLAY_SID_RESX_COL] & "x" & $aLayouts[$i][$iLAY_SID_RESY_COL] & ")" & @CRLF
		 ;Center
	  $sTempBody = $sTempBody & "Center:" & @CRLF
			;eg 23" viewable, 1920x1200 (16:10)
	  $sTempBody = $sTempBody & @TAB & Round($aLayouts[$i][$iLAY_CTR_INCH_DIAG_COL], $iRND_MS) & $sUnitCorrect & " viewable, " & _
						$aLayouts[$i][$iLAY_CTR_RESX_COL] & "x" & $aLayouts[$i][$iLAY_CTR_RESY_COL] & _
			   " (" & _GetFormattedAspect($aLayouts[$i][$iLAY_CTR_RESX_COL], $aLayouts[$i][$iLAY_CTR_RESY_COL]) & ")" & @CRLF
			;eg ~98.44 PPI
	  $sTempBody = $sTempBody & @TAB & "~" & Round($aLayouts[$i][$iLAY_CTR_DPI_COL],$iRND_MS) & " PPI" & @CRLF
		 ;Sides
	  $sTempBody = $sTempBody & "Sides:" & @CRLF
			;eg 17" viewable, 1280x1024 (5:4)
	  $sTempBody = $sTempBody & @TAB & Round($aLayouts[$i][$iLAY_SID_INCH_DIAG_COL],$iRND_MS) & $sUnitCorrect & " viewable, "
			   ;first must show orig res. 0-flag shows that stock resolution is used.
	  If $aLayouts[$i][$iRESXORI_COL] = 0 Then
		 $sTempBody = $sTempBody & $aLayouts[$i][$iLAY_SID_RESX_COL] & "x" & $aLayouts[$i][$iLAY_SID_RESY_COL]
	  Else		;lowered res being used, so different elem to grab original side res.
		 $sTempBody = $sTempBody & $aLayouts[$i][$iRESXORI_COL] & "x" & $aLayouts[$i][$iRESYORI_COL]
	  EndIf
				  ;aspect string
	  $sTempBody = $sTempBody & " (" & _GetFormattedAspect($aLayouts[$i][$iLAY_SID_RESX_COL], $aLayouts[$i][$iLAY_SID_RESY_COL]) & ")" & @CRLF
			;eg Use non-stock resolution: 800x600
	  If $aLayouts[$i][$iRESXORI_COL] <> 0 Then
		 $sTempBody = $sTempBody & @TAB & "Use lowered resolution: " & $aLayouts[$i][$iLAY_SID_RESX_COL] & "x" & $aLayouts[$i][$iLAY_SID_RESY_COL]
			   ;poorly-used res. not straight math, but what i personally think is commonly thought of as "stupidly-used"
		 If $aLayouts[$i][$iRESXORI_COL] >= 2560 And $aLayouts[$i][$iLAY_SID_RESX_COL] <= 1280 Or _
				  $aLayouts[$i][$iRESXORI_COL] >= 3840 And $aLayouts[$i][$iLAY_SID_RESX_COL] <= 1920 Then
			$sTempBody = $sTempBody & ". The sides' resolution is poorly used." & @CRLF
		 Else
			$sTempBody = $sTempBody & @CRLF
		 EndIf
	  EndIf
			;eg ~96.42 PPI
	  $sTempBody = $sTempBody & @TAB & "~" & Round($aLayouts[$i][$iLAY_SID_DPI_COL], $iRND_MS) & " PPI" & @CRLF
			;eg Position inner edge noticeably backward of center's bezel, still remaining bezel-to-bezel.
	  $sTempBody = $sTempBody & @TAB & "Position inner edge " & _GetSidePositionStr($aLayouts[$i][$iACCUM_IMG_COL]) & @CRLF
			;eg Total hang past center screen: [~0.88"]["zero or near-zero"][. Aesthetically pleasing][what ugly hang will look like]
			   ;pt1
	  $sTempBody = $sTempBody & @TAB & "Total perceived hang past center screen: "
	  If $aLayouts[$i][$iTTL_HANG_COL] > (0.25 * $fUnitCorrect) Or $aLayouts[$i][$iTTL_HANG_COL] < (-0.1 * $fUnitCorrect) Then			;@HARDCODED val based on physical testing
		 $sTempBody = $sTempBody & "~" & Round($aLayouts[$i][$iTTL_HANG_COL], $iRND_MS) & $sUnitCorrect
	  Else
		 $sTempBody = $sTempBody & "zero or near-zero"
	  EndIf		;pt2														;@HARDCODED val based on physical testing.
		 ;below is not perfect, but good enough. the remainging issue is that a medium-sized hang looks smaller on a huge-screened layout.
		 ;also sitting distance of huge layout makes hang look smaller.
		 ;so in some cases, longer hangs could appear either aesthetically pleasing or very ugly (small layout). which is what we are supposed to be tracking here, the PERCEPTION. but no big deal.
	  If $aLayouts[$i][$iTTL_HANG_COL] < (-0.1 * $fUnitCorrect) Then
		 $sTempBody = $sTempBody & ". Warning: Sides will look bit shorter than center."
		 Else
		 If $aLayouts[$i][$iTTL_HANG_COL] <= (1.2 * $fUnitCorrect) And $aLayouts[$i][$iTTL_HANG_COL] >= (-0.1 * $fUnitCorrect) Then
			$sTempBody = $sTempBody & ". Aesthetically pleasing"
		 Else
			   ;pt3, bad-hang described						@HARDCODED based on physical test results.
			If $aLayouts[$i][$iTTL_HANG_COL] > (3.3 * $fUnitCorrect) And $aLayouts[$i][$iTTL_HANG_COL] <= (3.6 * $fUnitCorrect) Then
			   $sTempBody = $sTempBody & ". Sides hang a little long. H-shape looks better, but requires graphics card support."
			Else
			   If $aLayouts[$i][$iTTL_HANG_COL] > (3.6 * $fUnitCorrect) And $aLayouts[$i][$iTTL_HANG_COL] < (7.2 * $fUnitCorrect) Then
				  $sTempBody = $sTempBody & ". Sides hang long. H-shape is basically needed, which requires graphics card support."
			   Else
				  If $aLayouts[$i][$iTTL_HANG_COL] >= (7.2 * $fUnitCorrect) Then
					 $sTempBody = $sTempBody & ".  Warning: Sides hang very long. H-shape is needed, which requires graphics card support. Mouse troughs & clearance to consider."
				  EndIf
			   EndIf
			EndIf
		 EndIf
	  EndIf

	  $sTempBody = $sTempBody & @CRLF
			;additional note
	  If $aLayouts[$i][$iLAY_CTR_RESY_COL] <= $aLayouts[$i][$iLAY_SID_RESY_COL] Then
		 $sTempBody = $sTempBody & @TAB & "Abnormally large sides, will work fine in either PLP or mixed-LLL." & @CRLF
	  EndIf
		 ;Final gaming area
	  $sTempBody = $sTempBody & "Final Gaming Area:" & @CRLF
			;shape of ctr & of sides (not of whole layout, which maybe FUTURE IMPROVEMENT)
			   ;(getSideShape requires side's actual gaming-aspect: sidePxHt / ctrPxHt)	FUTURE CODE CHANGE if user-option allow-short-sides? No, won't make much difference, as program will only ever accept bit-short sides.
				  ;pt1
	  $sTempBody = $sTempBody & @TAB & "Perceived shape: center is " & _GetCenterShape($aLayouts[$i][$iLAY_CTR_ASPECT_COL])
				  ;pt2
	  If $bIsOptLayoutPLP = True Then		;is this PLP or LLL?
		 $sTempBody = $sTempBody & ", sides have " & _GetSideShape($aLayouts[$i][$iLAY_SID_RESY_COL] / $aLayouts[$i][$iRESY_COL], $aLayouts[$i][$iLAY_CTR_ASPECT_COL]) & " width" & @CRLF
	  Else
		 $sTempBody = $sTempBody & ", sides have " & _GetSideShape($aLayouts[$i][$iLAY_SID_RESX_COL] / $aLayouts[$i][$iRESY_COL], $aLayouts[$i][$iLAY_CTR_ASPECT_COL]) & " width" & @CRLF
	  EndIf
			;Viewable size: ~##.#"
	  $sTempBody = $sTempBody & @TAB & "Viewable size: ~" & Round($aLayouts[$i][$iINCH_DIAG_COL],$iRND_SM) & $sUnitCorrect & @CRLF
			;eg Width: ~32.8"
	  $sTempBody = $sTempBody & @TAB & "Width: ~" & Round($aLayouts[$i][$iINCH_WD_COL],$iRND_SM) & $sUnitCorrect & @CRLF
			;eg Height: ~12.2"
	  $sTempBody = $sTempBody & @TAB & "Height: ~" & Round($aLayouts[$i][$iINCH_HT_COL],$iRND_SM) & $sUnitCorrect & @CRLF
			;eg Resolution: 3968x1200
	  $sTempBody = $sTempBody & @TAB & "Resolution: " & $aLayouts[$i][$iRESX_COL] & "x" & $aLayouts[$i][$iRESY_COL] & @CRLF
			;eg Pixel count: 4,761,600 (weight level 7/30)
	  $sTempBody = $sTempBody & @TAB & "Pixel count: " & _GetFormattedPxCount($aLayouts[$i][$iTTL_PX_CNT]) & " (weight level " & _GetWeightClass($aLayouts[$i][$iTTL_PX_CNT]) & ")" & @CRLF
			;eg Aspect ratio: 3.306666667 = 248:75, ~21:6.351, ~16:4.839
	  $sTempBody = $sTempBody & @TAB & "Aspect ratio: " & Round($aLayouts[$i][$iASPECT_COLB], $iRND_LG) & " = "& _GetFormattedAspect($aLayouts[$i][$iRESX_COL], $aLayouts[$i][$iRESY_COL], 1) & @CRLF
			;eg Perceived overall as ~98.44 PPI
	  $sTempBody = $sTempBody & @TAB & "Perceived overall as ~" & Abs(Round($aLayouts[$i][$iDPI_COL],$iRND_MS)) & " PPI"
			   ;([pretty] high PPI)
	  If $aLayouts[$i][$iDPI_COL] > 99 Then											;@HARDCODED val based on physical testing
		 $sTempBody = $sTempBody & " ("
		 If $aLayouts[$i][$iDPI_COL] < 106 Then $sTempBody = $sTempBody & "pretty "		;@HARDCODED val based on physical testing
		 $sTempBody = $sTempBody & "high PPI)" & @CRLF
	  Else 		;not hi ppi, just end the line
		 $sTempBody = $sTempBody & @CRLF
	  EndIf
			;eg PPI variance: ~2.02
	  $sTempBody = $sTempBody & @TAB & "PPI variance: ~" & Abs(Round($aLayouts[$i][$iDPI_VARIANCE_COL],$iRND_MS)) & " PPI"
			   ;close match
;	  If $aLayouts[$i][$iDPI_VARIANCE_COL] <= 0.35 Then								;@HARDCODED val based on physical testing. THIS COULD BE MADE BETTER: SHOULD BE BASED ON ACCUM... DUDE
	  If $aLayouts[$i][$iACCUM_IMG_COL] >= -6.25 And $aLayouts[$i][$iACCUM_IMG_COL] <= 4.25 Then		;@HARDCODED val based on physical testing. Same maxes as flexFit=False
		 $sTempBody = $sTempBody & " (a close match. Monitors will be flush or near-flush)" & @CRLF
	  Else
		 $sTempBody = $sTempBody & @CRLF
	  EndIf
			;curr: Accumulated vertical image variance: ~#.##mm (falls [just barely] within correctable range)
	  $sTempBody = $sTempBody & @TAB & "Accumulated vertical image variance: ~" & Abs(Round($aLayouts[$i][$iACCUM_IMG_COL],$iRND_MS)) & "mm (falls "
			   ;just barely (scary vals. This likely will never trigger unless "option:Gambler-enabled")
	  If $aLayouts[$i][$iACCUM_IMG_COL] > 15.6 Or $aLayouts[$i][$iACCUM_IMG_COL] < -18.6 Then		;@HARDCODED val based on physical testing
		 $sTempBody = $sTempBody & "just barely "
	  EndIf
	  $sTempBody = $sTempBody & "within proper range)" & @CRLF
			;separator line
	  $sBody = $sBody & $sTempBody & $sSeparator & @CRLF & @CRLF
	  $sTempBody = ""
   Next
	  ;Footnote
   $sFoot = _GetUserOptionStr() & @CRLF

   Return $sBody & $sFoot
EndFunc

Func _DumpTable(ByRef $aTableSource, $iTableType, $sSeparator = @TAB)
   Local $sFileOpen

   _FileCreate($aDUMP_FILENAME[$iTableType])	;creates or wipes prev content
   $sFileOpen = FileOpen($aDUMP_FILENAME[$iTableType], $FO_APPEND)	;required for append-mode.
	  ;add header
   FileWriteLine($sFileOpen, _GetTableHeader($iTableType))
	  ;add data
   _FileWriteFromArray($sFileOpen, $aTableSource, 0, Ubound($aTableSource), $sSeparator)
   FileClose($sFileOpen)
EndFunc

Func _DumpReport()
;dumps Match ALL report. this is only called after dump-match-all-table, when Layouts array is still filled with match-all-layouts.
   Local $sFileOpen

   _FileCreate($aDUMP_FILENAME[$iLAYOUT_REPORT])	;creates or wipes prev content
   $sFileOpen = FileOpen($aDUMP_FILENAME[$iLAYOUT_REPORT], $FO_APPEND)	;required for append-mode.
   FileWrite($sFileOpen, Ubound($aLayouts) & " LAYOUTS FOUND. This report is the verbose version of Layouts Table. See footer for options used." & @CRLF & @CRLF & _GetReport())
   FileClose($sFileOpen)
EndFunc

Func _GetHelp()
   Local $sFileOpen
   Local $sFileText
   Local $iHelpPosStart = 0
   Local $iHelpPosEnd = 0

   $sFileOpen = FileOpen($sSOURCECODE_NAME)
   If $sFileOpen = -1 Then
	  MsgBox ($MB_OK, "Missing File", "Help source-file not found: " & @CR & $sSOURCECODE_NAME)
	  Return
   EndIf

   $sFileText = FileRead($sFileOpen)
   $iHelpPosStart = StringInStr ($sFileText, "#cs") + 4
   $iHelpPosEnd = StringInStr ($sFileText, "#ce") - 1
   FileClose($sFileOpen)
   Return StringMid($sFileText, $iHelpPosStart, $iHelpPosEnd - $iHelpPosStart)
EndFunc

#EndRegion ###### Grunt Routines: Read & Write END ######
#Region ###### Grunt Routines: Get Extranious Report Details START ######

Func _GetTableHeader($iTableType, $iHeaderRowCount = 2)
;param iTableType is not actually needed, but seems less safer for program expansion (func could have just decided which table-type based on col-count)
   Local $aHeader[2]	;0=table-header-row, 1=column-header-row
   Local Const $iTBL_HEADER = 0
   Local Const $iCOLS_HEADER = 1
   Local Const $iBOTH_HEADER = 2
   Local Const $sUSAGE = "(notepad/excel). "
   Local $sUnits

	  ;title strings
		 ;for column (splits by commas, so don't use commas in-name.)
   Local Const $sCmnColsSnipA = "ResX,ResY,ViewableDiag,AspectDec,ViewableWidth,ViewableHeight,Ppi"
   Local Const $sCmnColsSnipB = "OrigResX,OrigResY"
   Local Const $sCmnColsSnipC = "SideOrigResX,SideOrigResY"
   Local Const $sLayoutColsSnipA = "PpiVariance,SID_AccumImg(vert_mm),TtlPxCount,TtlHang"
   Local Const $sLayoutColsSnipB = "CTR_ResX,CTR_ResY,CTR_ViewableDiag,CTR_Aspect,CTR_ViewableWidth,CTR_ViewableHeight,CTR_Ppi"
   Local Const $sLayoutColsSnipC = "SID_ResX,SID_ResY,SID_ViewableDiag,SID_Aspect,SID_ViewableWidth,SID_ViewableHeight,SID_Ppi,sort"
   Local $sTableColsName
		 ;for table (single-col line, commas mean nothing special)
   If $bIsOptInch = True Or $iTableType <> $iLAYOUT_TABLE Then
	  $sUnits = "Units: Inch (unless stated). "
   Else
	  $sUnits = "Units: cm (unless stated). "
   EndIf
   Local Const $aTableTitle[4] = ["ASPECT LIST " & $sUSAGE, _
							  "STOCK MONITORS LIST " & $sUSAGE & $sUnits, _
							  "ALT MONITORS LIST - lowered resolutions " & $sUSAGE & $sUnits, _
							  "LAYOUT LIST - " & Ubound($aLayouts) & " found " & $sUSAGE & _GetUserOptionStr()]
   Select
	  Case $iTableType = $iASPECT_TABLE
		 $sTableColsName = "ResX,ResY,AspectDec"
	  Case $iTableType = $iSTOCK_TABLE
		 $sTableColsName = $sCmnColsSnipA &","& $sCmnColsSnipB
	  Case $iTableType = $iALT_TABLE
		 $sTableColsName = $sCmnColsSnipA &","& $sCmnColsSnipB
	  Case $iTableType = $iLAYOUT_TABLE
		 $sTableColsName = $sCmnColsSnipA &","& $sCmnColsSnipC &","& $sLayoutColsSnipA &","& $sLayoutColsSnipB &","& $sLayoutColsSnipC
   EndSelect

   If $iHeaderRowCount = 2 Then
      Return String($aTableTitle[$iTableType] & @CRLF & StringReplace($sTableColsName, ",", @TAB))
   Else		;return only table-title row
	  Return $aTableTitle[$iTableType]
   EndIf
EndFunc

Func _GetUserOptionStr()
   Local $sMyOptions = ""

   $sMyOptions = "OPTIONS USED - Process Type: "
   Select
	  Case $iProcessType = $iPROC_COMPARE
		 $sMyOptions = $sMyOptions & "Screen compare. "
	  Case $iProcessType = $iPROC_FIND_SIDE
		 $sMyOptions = $sMyOptions & "Find side. "
	  Case $iProcessType = $iPROC_FIND_CTR
		 $sMyOptions = $sMyOptions & "Find center. "
	  Case $iProcessType = $iPROC_MATCH_ALL
		 $sMyOptions = $sMyOptions & "Match ALL. "
   EndSelect

   $sMyOptions = $sMyOptions & "Layout: "
   If $bIsOptLayoutPLP = True Then
	  $sMyOptions = $sMyOptions & "PLP. "
   Else
	  $sMyOptions = $sMyOptions & "Mixed-LLL. "
   EndIf

   $sMyOptions = $sMyOptions & "Units: "
   If $bIsOptInch = True Then
	  $sMyOptions = $sMyOptions & "Inch (unless stated). "
   Else
	  $sMyOptions = $sMyOptions & "cm (unless stated). "
   EndIf

   $sMyOptions = $sMyOptions & "Tweaks: " & _
   "flexFit " & $bIsOptFlexFit & ", " & _
   "noGambler " & $bIsOptNoGambler & ", " & _
   "allowAltSide (lowered resolution) " & $bIsOptAltSide & ", " & _
   "maxSideHang " & $fMaxSideHang & $sUnitCorrect & ", " & _
   "maxSideShort " & $fMaxSideShort & $sUnitCorrect & ". "

   Return $sMyOptions
EndFunc

Func _GetFormattedAspect($iResX, $iResY, $iFormatType = 0)
;calc GCD. Format to regular "#:#" (with occasional special formatting, eg 8:5=16:10), or to full "#:#, ~21:#.###, ~16:#.###" (latter used for layout's total aspect).
   Local $iTempResX = $iResX
   Local $iTempResY = $iResY
   Local $iRegResX
   Local $iRegResY
   Local $sRegAspect
   Local $iMod
   Local Const $iFMT_REG = 0
;   Local Const $iFMT_FULL = 1

   Do
	  $iMod = Mod($iTempResX, $iTempResY)
	  If $iMod = 0 Then
		 $iRegResX = $iResX / $iTempResY
		 $iRegResY = $iResY / $iTempResY
	  EndIf
	  $iTempResX = $iTempResY
	  $iTempResY = $iMod
   Until $iMod = 0

   $sRegAspect = String($iRegResX & ":" & $iRegResY)

   If $iFormatType = $iFMT_REG Then
		 ;replace weird aspects w readable
	  If $sRegAspect = "8:5" Then $sRegAspect = "16:10"
	  If $sRegAspect = "64:27" Then $sRegAspect = $sRegAspect & ", ~21:9"
	  If $sRegAspect = "683:384" Then $sRegAspect = $sRegAspect & ", ~16:9"
	  If $sRegAspect = "683:199" Then $sRegAspect = $sRegAspect & ", ~17:5"
	  Return $sRegAspect
   Else			;FMT_FULL, for layout's total aspect. eg "248:75, ~21:6.351, ~16:4.839"
	  Return String($sRegAspect &", ~21:"& Round(21/($iResX/$iResY), 3)&", ~16:"& Round(16/($iResX/$iResY), 3))
   EndIf
EndFunc

Func _GetFormattedPxCount($iPxCount)
   Local $sComma
   If StringLen($iPxCount) > 3 Then
      $sComma = StringLeft($iPxCount, StringLen($iPxCount) - 3) & "," & StringRight($iPxCount, 3)
      Do
         If Not StringInStr(StringLeft($sComma, 4), ",") Then
            $sComma = StringLeft($sComma, StringInStr($sComma, ",") - 4) & "," & StringRight($sComma, StringLen($sComma) - StringInStr($sComma, ",") + 4)
		 EndIf
	  Until StringInStr(StringLeft($sComma, 4), ",")
   EndIf
   Return $sComma
EndFunc

Func _GetSidePositionStr($fAccumImgMm)
;dictionary-type function, answers question: if accum_img = #mm, then where should sides be positioned to get perfect perceived px line-up?
;- ugly function, positions listed are @HARDCODED based on physical testing results. it's bit rough but good enough. there is positional-type-math for it, but it's beyond me atm.
;- dead-accurate position-response would depend upon 3D bezel-thickness, which this program cannot access (can't know bezel thickness in DB).
;- - 3D bezel-details could have been UserInput (bezels could be known this way). but ugh no-one cares that much & interface would suck & im too lazy to code this minute & complex detail.
;- - So text is written generically & bit loose, for user to consider their own bezel sizes & decide accordingly whether or not layout will work out for them.
   Local $fAccum = Round($fAccumImgMm,2)
   Local Const $iALL_DECENT = 37
   Local $aPosition[$iALL_DECENT][2]
   Local $fDiff = 100.1		;rough way-high starter, will be overwritten
   Local $iClosestKey
	  ;init strings per accum_img. (note to self: historically was hint less, for use in xls, to work better with vlookup's slightly-screwy "offset" results -- ie xls favored one direction, this program will not)
   $aPosition[0][0] = -19
   $aPosition[0][1] = "forward of center's bezel, hugging it extremely loosely & hiding one bezel (requires unaccepable gap forward of center's bezel, unless using quite chunky bezels)."
   $aPosition[1][0] = -18.8
   $aPosition[1][1] = "forward of center's bezel, hugging it extremely loosely & hiding one bezel (requires significant gap forward of center's bezel. Fine if using quite chunky bezels, not ideal on tiny bezels)."
   $aPosition[2][0] = -18
   $aPosition[2][1] = "forward of center's bezel, hugging it very loosely & hiding one bezel (requires significant but acceptable gap forward of center's bezel on medium-sized bezels. Not ideal on tiny bezels)."
   $aPosition[3][0] = -17
   $aPosition[3][1] = "forward of center's bezel, hugging it fairly loosely & hiding one bezel (requires fair-sized  but acceptable gap forward of center's bezel on medium-sized bezels. Less ideal on tiny bezels)."
   $aPosition[4][0] = -16
   $aPosition[4][1] = "forward of center's bezel, hugging it bit loosely & hiding one bezel (requires fairly small gap forward of center's bezel on medium-sized bezels)."
   $aPosition[5][0] = -15
   $aPosition[5][1] = "forward of center's bezel, hugging it fairly tight & hiding one bezel (requires small gap forward of center's bezel on medium-sized bezels)."
   $aPosition[6][0] = -14
   $aPosition[6][1] = "forward of center's bezel, hugging it fairly tight & hiding one bezel (requires slight gap forward of center's bezel on medium-sized bezels)."
   $aPosition[7][0] = -13
   $aPosition[7][1] = "forward of center's bezel, hugging it near-tight & hiding one bezel (on medium-sized bezels)."
   $aPosition[8][0] = -12
   $aPosition[8][1] = "forward of center's bezel, hugging it tight or near-tight & hiding one bezel (on medium-sized bezels)."
   $aPosition[9][0] = -11
   $aPosition[9][1] = "forward of center's bezel, hugging it tight & hiding one bezel (on medium-sized bezels)."
   $aPosition[10][0] = -10
   $aPosition[10][1] = "very noticeably forward of center's bezel, likely still remaining bezel-to-bezel (but good chance of hide-a-bezel if small bezels)."
   $aPosition[11][0] = -9
   $aPosition[11][1] = "very noticeably forward of center's bezel, likely still remaining bezel-to-bezel (but small chance of hide-a-bezel if small bezels)."
   $aPosition[12][0] = -8
   $aPosition[12][1] = "noticeably forward of center's bezel, still remaining bezel-to-bezel."
   $aPosition[13][0] = -7
   $aPosition[13][1] = "noticeably forward of center's bezel, still remaining bezel-to-bezel."
   $aPosition[14][0] = -6
   $aPosition[14][1] = "slightly forward of center's bezel, still remaining bezel-to-bezel."
   $aPosition[15][0] = -5
   $aPosition[15][1] = "slightly forward of center's bezel, still remaining bezel-to-bezel."
   $aPosition[16][0] = -4
   $aPosition[16][1] = "very slightly forward of center's bezel, still remaining bezel-to-bezel."
   $aPosition[17][0] = -3
   $aPosition[17][1] = "very slightly forward of center's bezel, still remaining bezel-to-bezel."
   $aPosition[18][0] = -2
   $aPosition[18][1] = "flush or near-flush with center's bezel, remaining bezel-to-bezel."
   $aPosition[19][0] = -1
   $aPosition[19][1] = "flush with center's bezel, remaining bezel-to-bezel."
   $aPosition[20][0] = 1
   $aPosition[20][1] = "flush with center's bezel, remaining bezel-to-bezel."
   $aPosition[21][0] = 2
   $aPosition[21][1] = "flush or near-flush with center's bezel, remaining bezel-to-bezel."
   $aPosition[22][0] = 3
   $aPosition[22][1] = "very slightly backward of center's bezel, still remaining bezel-to-bezel."
   $aPosition[23][0] = 4
   $aPosition[23][1] = "slightly backward of center's bezel, still remaining bezel-to-bezel."
   $aPosition[24][0] = 5
   $aPosition[24][1] = "noticeably backward of center's bezel, still remaining bezel-to-bezel."
   $aPosition[25][0] = 6
   $aPosition[25][1] = "noticeably backward of center's bezel, still remaining bezel-to-bezel."
   $aPosition[26][0] = 7
   $aPosition[26][1] = "very noticeably backward of center's bezel, likely still remaining bezel-to-bezel (but small chance of hide-a-bezel if small bezels)."
   $aPosition[27][0] = 8
   $aPosition[27][1] = "very noticeably backward of center's bezel, likely still remaining bezel-to-bezel (but good chance of hide-a-bezel if small bezels)."
   $aPosition[28][0] = 9
   $aPosition[28][1] = "backward of center's bezel, hugging it tight & hiding one bezel (on medium-sized bezels)."
   $aPosition[29][0] = 10
   $aPosition[29][1] = "backward of center's bezel, hugging it tight or near-tight & hiding one bezel (on medium-sized bezels)."
   $aPosition[30][0] = 11
   $aPosition[30][1] = "backward of center's bezel, hugging it near-tight & hiding one bezel (on medium-sized bezels)."
   $aPosition[31][0] = 12
   $aPosition[31][1] = "backward of center's bezel, hugging it fairly tight & hiding one bezel (requires slight gap behind center's bezel on medium-sized bezels)."
   $aPosition[32][0] = 13
   $aPosition[32][1] = "backward of center's bezel, hugging it bit loosely & hiding one bezel (requires small gap behind center's bezel on medium-sized bezels)."
   $aPosition[33][0] = 14
   $aPosition[33][1] = "backward of center's bezel, hugging it fairly loosely & hiding one bezel (requires fair-sized but reasonable gap behind center's bezel on medium-sized bezels. Less ideal on tiny bezels)."
   $aPosition[34][0] = 15
   $aPosition[34][1] = "backward of center's bezel, hugging it very loosely & hiding one bezel (requires significant but reasonable gap behind center's bezel on medium-sized bezels. Not ideal on tiny bezels)."
   $aPosition[35][0] = 15.8
   $aPosition[35][1] = "backward of center's bezel, hugging it extremely loosely & hiding one bezel (requires significant gap behind center's bezel. Fine if using quite chunky bezels, not ideal on tiny bezels)."
   $aPosition[36][0] = 16
   $aPosition[36][1] = "backward of center's bezel, hugging it extremely loosely & hiding one bezel (requires unaccepable gap behind center's bezel, unless using quite chunky bezels)."
	  ;find closest match
   For $i = 0 To $iALL_DECENT - 1
	  If Abs($fAccum - $aPosition[$i][0]) < $fDiff Then
		 $fDiff = Abs($fAccum - $aPosition[$i][0])
		 $iClosestKey = $i
	  Else
		 $i = $iALL_DECENT - 1
	  EndIf
   Next

   Return $aPosition[$iClosestKey][1]
EndFunc

Func _GetWeightClass($iTtlPxCount)
;standardized to a more readable weight-level.
;- may need to be adjusted in distant future, "when 31+/30" happens
;- works well for curr-existing data-range: 2,228,736 (1/30) TO 14,515,200 (30/30)
   Return String(ROUND(2.42*($iTtlPxCount/1000000)-4.8,0) & "/30")
EndFunc

Func _GetSideShape($fAspectGamingSide, $iAspectCenter)
;dictionary-type function, returning perceived shape of side. This is impacted by center's aspect.
;the final description lookup will include an offset, as description varies-by-1-offset (based on ctr's aspect, due to overall layout perception).
;params:
;- arg1 is not just the side's aspect, but rather the aspect after it is cropped to gaming-area (ie using ctr's height).
;- arg2 is ctr, needed to help describe the sides' shape. this because the ctr has a strong impact on what sides will look like (& indeed the entire layout).
;FUTURE CHANGE: for shortSides... maybe. Seems like it wouldn't matter much because program won't let super-short sides through.
   Local Const $iTTL_SHAPES = 9
   Local Const $aShapeStr[$iTTL_SHAPES] = ["very very skinny", "very skinny", "skinny", "slim", "medium", "semi-fat", "fat", "very fat", "very very fat"]
   Local Const $iOFFSET_CTR_VERYTALL = 2	;-1	;4:3 & 5:4 (16:12 & 16:12.8)
   Local Const $iOFFSET_CTR_TALL = 1		;16:10 center screen
   Local Const $iOFFSET_CTR_MED = 0			;16:9 center screen. no-offset
   Local Const $iOFFSET_CTR_SHORT = -1		;21:9 center screen
   Local $iOffset
   Local $iOffsetMixLLL = 0	;mixed-LLL gamers have a different expectation, fatter is normal. so the offset should in their case be dropped further by one.
   Local $iAspectType
   Local $sShapeHtPrefix = ""
	  ;process sides:
		 ;decide the offset & text-prefix
   If $bIsOptLayoutPLP = False Then $iOffsetMixLLL = -1
   Select
		 ;ctr: tall & slim 4:3 or 5:4
	  Case $iAspectCenter <= 1.5
		 $iOffset = $iOFFSET_CTR_VERYTALL
		 $sShapeHtPrefix = "tall "
		 ;ctr: semi-tall & semi-slim 16:10
	  Case $iAspectCenter > 1.5 And <= $iAspectCenter 1.7
		 $iOffset = $iOFFSET_CTR_TALL
		 $sShapeHtPrefix = "tall "
		 ;ctr: semi-wide & semi-short 16:9. The standard, average & best overall ctr for PLP... imo
	  Case $iAspectCenter > 1.7 And $iAspectCenter < 2
		 $iOffset = $iOFFSET_CTR_MED
		 ;ctr: extremely wide & extremely short 21:9
	  Case $iAspectCenter >= 2
		 $iOffset = $iOFFSET_CTR_SHORT
		 $sShapeHtPrefix = "small "	;"short" could instead have been used here, but it is ultimately perceived "small", a more descriptive observation.
   EndSelect

	  ;side's game-area aspect ranges, from skinniest to fattest.
		 ;"expanded" notes below were for integration into program: no gaps, which will encompass many rare/non-existent cases.
   Select
	  Case $fAspectGamingSide <= 0.3
		 ;OUTSIDE-VERY-SKINNY (w 16:9 ctr) & catch-all-outer (very-very-skinny)
			;tested common: NONE EVER, UGLY & VERY RARE/NOEXIST
			;expanded:
			   ;Range: <=0.3. RangeSize: outer, rare huge range of non-existent & garbage layouts.
		 $iAspectType = 0
	  Case $fAspectGamingSide >0.3 And $fAspectGamingSide <= 0.4
		 ;VERY-SKINNY (w 16:9 ctr)
			;tested common: NONE EVER, UGLY & VERY RARE/NOEXIST
			;expanded:
			   ;Range: >0.3 And <=0.4. RangeSize: 0.1 outer, rare range of non-existent & garbage layouts.
		 $iAspectType = 1
	  Case $fAspectGamingSide > 0.4 And $fAspectGamingSide <= 0.5
		 ;SKINNY (w 16:9 ctr)
			;tested common:
			   ;Range: 0.368-0.5. RangeSize: 0.132 (BIG RANGE...)
			;expanded:
			   ;Range: >0.4 And <=0.5. RangeSize: 0.1 (normalized to smaller range)
		 $iAspectType = 2
	  Case $fAspectGamingSide > 0.5 And $fAspectGamingSide < 0.71
		 ;SLIM (w 16:9 ctr)
			;tested common:
			   ;Range: 0.62-0.67. RangeSize: 0.05 (proper-sized range)
			;expanded:
			   ;Range: >0.5 And <0.71. RangeSize: 0.21 (normalized to bigger range)
		 $iAspectType = 3
	  Case $fAspectGamingSide >= 0.71 And $fAspectGamingSide < 0.8
		 ;MEDIUM (w 16:9 ctr)
			;tested common:
			   ;Range: 0.71-0.762. RangeSize: 0.052 (proper-sized range)
			;expanded:
			   ;Range: >=0.71 And <0.8. RangeSize: 0.09 (normalized to bigger range)
		 $iAspectType = 4
	  Case $fAspectGamingSide >= 0.8 And $fAspectGamingSide < 0.88
		 ;SEMI-FAT (w 16:9 ctr)
			;tested common:
			   ;Range: 0.8-0.8572. RangeSize: 0.0572 (proper-sized range, hint big)
			;expanded:
			   ;Range: >=0.8 And <0.88. RangeSize: 0.08 (normalized to bigger range)
		 $iAspectType = 5
	  Case $fAspectGamingSide >= 0.88 And $fAspectGamingSide < 1.1
		 ;FAT (w 16:9 ctr)
			;tested common:
			   ;Range: 0.88-1. RangeSize: 0.12 (SMALL RANGE...)
			;expanded:
			   ;Range: >=0.88 And <1.1. RangeSize: 0.22 (normalized to bigger range)
		 $iAspectType = 6
	  Case $fAspectGamingSide >= 1.1 And $fAspectGamingSide <= 1.126
		 ;VERY-FAT (w 16:9 ctr)
			;tested common:
			   ;Range: 1.1-1.125. RangeSize: 0.025 (bit small range)
			;expanded:
			   ;Range: >=1.1 And <=1.126. RangeSize: 0.026 (normalized to about same)
		 $iAspectType = 7
	  Case $fAspectGamingSide > 1.126
		 ;OUTSIDE-VERY-FAT (w 16:9 ctr) & catch-all-outer (very-very-fat)
			;tested common: NONE EVER, UGLY & VERY RARE/NOEXIST
			;expanded:
			   ;Range: >1.126. RangeSize: outer, rare huge range of non-existent & garbage layouts
		 $iAspectType = 8
   EndSelect

   $iAspectType = $iAspectType + $iOffset + $iOffsetMixLLL

	  ;move any outer hits to its almost-outer range.
   If $iAspectType > ($iTTL_SHAPES - 1) Then $iAspectType = ($iTTL_SHAPES - 1)
   If $iAspectType < 0 Then $iAspectType = 0

   Return $sShapeHtPrefix & $aShapeStr[$iAspectType]
EndFunc

Func _GetCenterShape($iAspectCenter)
;these perceptions @HARDCODED based on physical testing. btw this center-perception impacts the entire-layout-perception, though may not code about that
   Select
	  Case $iAspectCenter <= 1.5		;4:3, 5:4 (1.33 & 1.25)
		 Return "tall & slim"
	  Case $iAspectCenter > 1.5 And <= $iAspectCenter 1.65	;16:10	(1.6)
		 Return "semi-tall & semi-slim"
	  Case $iAspectCenter > 1.65 And $iAspectCenter < 2		;16:9	(1.78)
		 Return "semi-wide & semi-short"
	  Case $iAspectCenter >= 2			;21:9	(2.37-2.39)
		 Return "extremely wide & extremely short"
   EndSelect
EndFunc

;Func _GetLayoutShape()
   ;!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
   ;THIS WILL BE A DESCRIPTION BASED ON LAYOUT'S TOTAL ASPECT, turned into the perception of these.
   ;IT IS OPTIONAL, being the least important of the shapes to verbalize, because user has a clearer idea of its shape based on:
   ;- ctr-shape & sid-shape combined (they will see these stated)
   ;- layout-ht-inch & layout-wd-inch (not layout-diag-inch, that doesn't help here)
   ;I SHOULD STILL DESCRIBE IT. IT CAN BE MADE CLEARER. BUT IT IS ANNOYING TO CODE.
   ;- it will require significant data-trolling (data i have, but must be massaged)
   ;- this should also be used to describe how encroaching the inner bezels will feel.
   ;this is a future improvement i think, which may never be implemented. out of patience atm, sick of coding for shapes & the result is not central.
;EndFunc
;Func _GetPerceivedHangStr()
;EndFunc
#EndRegion ###### Grunt Routines: Get Extranious Report Details END ######