Tag Archives: lesson

Person facing a security gate undergoing facial recognition scan on a digital device

When “Consent” Isn’t a Choice: Why Being Forced to Hand Over Your Face Should Worry All of Us

Imagine buying tickets online, from one of the biggest ticketing platforms, for years without a problem. Then one day your account is quietly frozen. You can still see your past orders, but you can no longer buy anything. There was no warning, no explanation of what you did wrong, and no human you can talk to. To get your account working again, the company tells you there is exactly one path forward: submit a scan of your face to a third-party verification company.

That’s it. No document check. No phone call. No manual review. A facial scan, or nothing.

This is happening now, to ordinary customers, at major platforms. And while it might look like a minor inconvenience, it touches some of the most important protections we have under data protection law. Here’s why it matters — not just for one person, but for everyone.

The problem with calling it “consent”

Your face is not like an email address or a postcode. Under the GDPR, a facial scan used to identify you is special-category biometric data — the most sensitive tier of personal information, alongside health records and religious beliefs. The law sets a deliberately high bar for processing it. In most consumer situations, the only realistic legal basis is your explicit consent.

But consent has a precise legal meaning. It must be:

  • Freely given — a genuine choice, with no penalty for saying no.
  • Specific and informed — you know exactly what you’re agreeing to and who gets your data.
  • Explicit — a clear, affirmative act, not something inferred from your behaviour.

Now hold that definition against how these schemes actually work. You’re told your account will stay restricted unless you provide the scan. When you ask whether there’s any other way to prove who you are, you’re told — in writing — that there is no alternative, and that the facial scan is a condition of using the service again.

Pause on that. If something is a condition with no alternative, it is not a choice. And if it is not a choice, it cannot be “freely given consent.” You can’t dress up a requirement as a favour by calling it consent on the sign-up screen. Regulators and the courts have been clear about this for years: consent that is bundled into service access, and backed by a penalty for refusing, is not valid consent at all.

There’s an even simpler tell. Some of these systems treat you as having “consented” merely by proceeding with the verification. But consent to biometric processing must be explicit — a deliberate, unambiguous act. “You clicked next, so you agreed” is the opposite of explicit.

The illusion of alternatives

Companies defending these schemes often point to “options.” Look closely and the options tend to evaporate:

  • “Just delete your account and make a new one.” Except account deletion is frequently blocked if you’re holding tickets or orders for a future event — so you can’t leave even if you want to. And a new account runs through the same risk-scoring system, so it can be flagged and sent down the exact same biometric funnel.
  • “Completing the check restores access.” Sometimes the fine print admits it doesn’t guarantee anything.

When every exit is a dead end, the “choice” is theatre. What’s really happening is compulsory biometric collection wearing a consent costume.

Decisions made by a machine, with no one accountable

Many of these account restrictions are triggered by an automated risk-scoring model — an algorithm that decides you look suspicious. You’re rarely told what data points it used, how it weighed them, or why you specifically were flagged.

The GDPR anticipated exactly this. It gives people rights around decisions made solely by automated means that significantly affect them, including the right to meaningful human intervention and an explanation. But “human review” only counts if a human can actually reach a different outcome. If the review’s only possible result is “comply with what the algorithm already demanded,” and the company won’t explain how it reached its conclusion, that isn’t meaningful oversight. It’s a rubber stamp with a person’s name on it.

Transparency you were never given

Data protection law requires companies to tell you, up front, what they do with your data and who they share it with. Yet in several of these cases:

  • The privacy notice names some fraud-screening partners but not the biometric verification provider actually collecting your face.
  • The notice may even state the company doesn’t typically make automated decisions — while an automated model is quietly freezing accounts.

You cannot meaningfully consent to something you were never told was happening. Transparency isn’t a nicety; it’s the foundation the rest of the framework stands on.

Collecting more than necessary — and keeping it too long

Two more principles are at stake:

  • Data minimisation — you should only collect what’s genuinely necessary. If a document check or manual review can confirm someone’s identity, insisting on a facial scan and refusing every less-intrusive method suggests the goal is to harvest biometrics, not to verify identity.
  • Storage limitation — sensitive data shouldn’t be kept longer than needed. When biometric data from failed or refused checks is retained for years — often because the company chose a long retention period, not because any law requires it — that’s a serious overreach.

Why this is bigger than tickets

It’s tempting to shrug this off. It’s just an entertainment account, right? But the principle scales terrifyingly well. Once we accept “hand over your biometrics or lose access” as a normal way to use everyday services, the same logic can be applied to banking, travel, utilities, healthcare portals — anything. Your face is not a password you can change if it leaks. Once it’s captured, scored, shared with third parties, and stored, you have permanently lost control of it.

Biometric coercion normalised in low-stakes contexts becomes biometric coercion everywhere.

What people can actually do

If you find yourself in this position:

  1. Ask, in writing, for the specific legal basis for processing your biometric data, and request a non-biometric alternative (document check or manual review).
  2. Request the logic behind any automated decision affecting you, and ask for genuine human review.
  3. Make a Subject Access Request to see what data they hold and how it’s being used.
  4. Keep every reply. A company’s own written words — “there is no alternative,” “this is a condition” — are often the strongest evidence that its “consent” basis doesn’t hold up.
  5. Complain to your data protection authority. In Ireland that’s the Data Protection Commission; across the EU, every country has one. Regulators can investigate lawful basis, transparency, automated decision-making, and retention — and can order companies to change course.

The bottom line

Fraud prevention is a legitimate goal. Nobody disputes that companies need to protect their platforms. But the law is clear that fighting fraud does not give a company a blank cheque to demand the most sensitive data you have, strip away real alternatives, hide who’s involved, and then call the result “consent.”

Consent means the freedom to say no. The moment saying no costs you the service, it stops being consent — and starts being coercion. Recognising that difference, and pushing back when the line is crossed, is how we keep it from becoming the default for everything.

Fix Wi-Fi 6 Gaming Lag: Essential Tips

Wi‑Fi 6 Gaming Fixes

If when streaming Xbox Cloud Gaming your ethernet wired PC plays perfectly but the same games over Wi‑Fi feel laggy, the issue is almost always latency instability (ping spikes), not raw download speed. This post explains why that happens, what you can change on the PC only (no router access required), includes a ready PowerShell ISE script that applies recommended Wi‑Fi and Bluetooth coexistence tweaks and generates a one‑click revert script, and shows exactly how to run the revert and enable Edge hardware acceleration.

Why Wi‑Fi jitter breaks cloud gaming

  • Speed ≠ stability. High Mbps only matters if latency is steady. Frequent ping spikes cause input lag, stutter, and low stream quality.
  • Bluetooth and Wi‑Fi can collide. Bluetooth uses 2.4 GHz and can interfere with Wi‑Fi if the adapter shares radios or if Wi‑Fi falls back to 2.4 GHz.
  • Driver and power settings matter. Adapter power saving, roaming aggressiveness, and transmit power affect micro‑disconnects and jitter.
  • DNS and network stacks help with lookup delays and occasional stutter but won’t fix large routing or interference problems.

What you can do on the PC now

Priority changes (PC only)

  • Prefer 5 GHz and ensure the adapter is using 802.11ax/802.11ac when available.
  • Disable adapter power saving and set roaming aggressiveness to low.
  • Enable Bluetooth coexistence (driver option) so Wi‑Fi and BT coordinate.
  • Set DNS to a fast public resolver such as 1.1.1.1 and 8.8.8.8 and flush DNS.
  • Close background network apps and enable Game Mode in Windows.
  • Temporarily disable other Bluetooth devices while gaming to test for interference.
  • Test with continuous ping to a stable host (for example 8.8.8.8) and watch for spikes.

Script: apply changes and create a revert script

How to use: open PowerShell ISE as Administrator, paste the script below into a new file, save, then press Run Script (F5). The script will back up current settings to %USERPROFILE%\WiFiGamingBackup, apply recommended changes, and write a revert script in the same folder.

WARNING: Use the below at your own risk

powershell

<#
Optimize-WiFi-BT-WithRollback.ps1
Run in PowerShell ISE as Administrator.
- Backs up adapter advanced properties, DNS, Bluetooth service state, and power plan to JSON.
- Applies recommended settings for gaming over Wi-Fi while enabling Bluetooth coexistence.
- Writes a revert script that restores the saved state.
#>
# Require admin
If (-not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) {
Write-Error "Run PowerShell ISE as Administrator."
Break
}
Write-Host "Starting Wi‑Fi and Bluetooth optimization with rollback generation..." -ForegroundColor Cyan
# 1) Identify active Wi-Fi adapter
$wifi = Get-NetAdapter -Physical | Where-Object { $_.Status -eq 'Up' -and ($_.InterfaceDescription -match 'Wireless|Wi-Fi|WLAN' -or $_.Name -match 'Wi-Fi|Wireless|WLAN') } | Select-Object -First 1
if (-not $wifi) {
Write-Error "No active Wi‑Fi adapter found. Ensure Wi‑Fi is enabled and connected."
exit 1
}
$adapterName = $wifi.Name
Write-Host "Active adapter:" $adapterName -ForegroundColor Green
# 2) Prepare backup folder and file
$backupDir = Join-Path $env:USERPROFILE "WiFiGamingBackup"
if (-not (Test-Path $backupDir)) { New-Item -Path $backupDir -ItemType Directory | Out-Null }
$timestamp = (Get-Date).ToString("yyyyMMdd_HHmmss")
$backupFile = Join-Path $backupDir "WiFiGamingBackup_$timestamp.json"
# 3) Collect current state
$advProps = Get-NetAdapterAdvancedProperty -Name $adapterName | Select-Object DisplayName, DisplayValue
$ifIndex = (Get-NetAdapter -Name $adapterName).ifIndex
$currentDns = (Get-DnsClientServerAddress -InterfaceIndex $ifIndex -ErrorAction SilentlyContinue).ServerAddresses
$btService = Get-Service -Name bthserv -ErrorAction SilentlyContinue
$btState = if ($btService) { @{ Exists = $true; Status = $btService.Status; StartType = (Get-WmiObject -Class Win32_Service -Filter "Name='bthserv'").StartMode } } else { @{ Exists = $false } }
$powerOut = powercfg -getactivescheme 2>&1
$powerGuid = if ($powerOut -match '\(([^)]+)\)') { ($powerOut -split '\s+')[3] } else { $null }
$backup = @{
Timestamp = $timestamp
AdapterName = $adapterName
AdvancedProperties = $advProps
DnsServers = $currentDns
BluetoothService = $btState
PowerPlanGuid = $powerGuid
}
$backup | ConvertTo-Json -Depth 6 | Out-File -FilePath $backupFile -Encoding UTF8
Write-Host "Backup written to $backupFile" -ForegroundColor Green
# 4) Helper to set adapter advanced property if present
function Set-IfAvailable {
param([string]$Adapter, [string]$DisplayName, [string]$DisplayValue)
$prop = Get-NetAdapterAdvancedProperty -Name $Adapter | Where-Object { $_.DisplayName -eq $DisplayName }
if ($prop) {
try {
Set-NetAdapterAdvancedProperty -Name $Adapter -DisplayName $DisplayName -DisplayValue $DisplayValue -NoRestart -ErrorAction Stop
Write-Host "Set '$DisplayName' -> '$DisplayValue'"
} catch {
Write-Warning "Failed to set '$DisplayName' -> '$DisplayValue': $_"
}
} else {
Write-Host "Skipping missing property: $DisplayName" -ForegroundColor DarkYellow
}
}
# 5) Apply recommended settings (tries multiple common DisplayName variants)
$settings = @(
@{Name='Preferred Band'; Value='Prefer 5 GHz band'},
@{Name='Preferred Band'; Value='3. Prefer 5GHz band'},
@{Name='Band'; Value='5 GHz'},
@{Name='Wireless Mode'; Value='802.11ax/802.11ac'},
@{Name='Wireless Mode'; Value='802.11ax'},
@{Name='Roaming Aggressiveness'; Value='Lowest'},
@{Name='Roaming Aggressiveness'; Value='1 - Lowest'},
@{Name='Power Save Mode'; Value='Disabled'},
@{Name='Power Save Mode'; Value='Off'},
@{Name='U-APSD Support'; Value='Disabled'},
@{Name='Transmit Power'; Value='Highest'},
@{Name='Transmit Power'; Value='100%'},
@{Name='Bluetooth Collaboration'; Value='Enabled'},
@{Name='Bluetooth Coexistence'; Value='Enabled'},
@{Name='Bluetooth Collaboration Mode'; Value='Enabled'}
)
Write-Host "`nApplying adapter settings..." -ForegroundColor Cyan
foreach ($s in $settings) { Set-IfAvailable -Adapter $adapterName -DisplayName $s.Name -DisplayValue $s.Value }
# Channel width attempts
Set-IfAvailable -Adapter $adapterName -DisplayName 'Channel Width for 5GHz' -DisplayValue '80 MHz'
Set-IfAvailable -Adapter $adapterName -DisplayName 'Channel Width for 5GHz' -DisplayValue 'Auto'
Set-IfAvailable -Adapter $adapterName -DisplayName 'Channel Width' -DisplayValue '80 MHz'
# 6) Set DNS servers to Cloudflare and Google
try {
Write-Host "`nSetting DNS servers to 1.1.1.1 and 8.8.8.8"
Set-DnsClientServerAddress -InterfaceIndex $ifIndex -ServerAddresses @('1.1.1.1','8.8.8.8') -ErrorAction Stop
Write-Host "DNS updated" -ForegroundColor Green
} catch {
Write-Warning "Failed to set DNS: $_"
}
# 7) Flush DNS and reset network stacks
Write-Host "`nFlushing DNS and resetting network stacks..."
try {
ipconfig /flushdns | Out-Null
netsh winsock reset | Out-Null
netsh int ip reset | Out-Null
Write-Host "Network stacks reset. Reboot recommended." -ForegroundColor Green
} catch {
Write-Warning "Network reset commands encountered an error: $_"
}
# 8) Bluetooth service handling option (default: do not stop)
$stopBluetooth = $false
if ($stopBluetooth) {
try {
if ($btService -and $btService.Status -ne 'Stopped') {
Stop-Service -Name bthserv -Force -ErrorAction Stop
Set-Service -Name bthserv -StartupType Disabled
Write-Host "Bluetooth service stopped and disabled."
}
} catch {
Write-Warning "Could not stop Bluetooth service: $_"
}
} else {
Write-Host "`nBluetooth service left unchanged. To auto-stop Bluetooth set `$stopBluetooth = $true` in this script and re-run." -ForegroundColor Yellow
}
# 9) Set power plan to High Performance if available
try {
$high = powercfg -l | Select-String -Pattern "High performance|High Performance"
if ($high) {
$guid = ($high -split '\s+')[3]
powercfg -setactive $guid
Write-Host "Power plan set to High Performance."
} else {
Write-Host "High Performance plan not found; skipping."
}
} catch {
Write-Warning "Power plan change failed: $_"
}
# 10) Generate revert script that restores saved values
$revertPath = Join-Path $backupDir "Revert-WiFiGamingChanges_$timestamp.ps1"
$revertScript = @"
<#
Revert-WiFiGamingChanges_$timestamp.ps1
Restores adapter advanced properties, DNS, Bluetooth service state, and power plan from:
$backupFile
Run as Administrator.
#>
If (-not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) {
Write-Error "Run PowerShell as Administrator."
Break
}
\$backup = \$null
try {
\$backup = Get-Content -Path '$backupFile' -Raw | ConvertFrom-Json
} catch {
Write-Error "Could not read backup file: $backupFile"
exit 1
}
\$adapter = \$backup.AdapterName
# Restore advanced properties
foreach (\$p in \$backup.AdvancedProperties) {
try {
Set-NetAdapterAdvancedProperty -Name \$adapter -DisplayName \$p.DisplayName -DisplayValue \$p.DisplayValue -NoRestart -ErrorAction Stop
Write-Host "Restored \$($p.DisplayName) -> \$($p.DisplayValue)"
} catch {
Write-Warning "Failed to restore \$($p.DisplayName): \$_"
}
}
# Restore DNS
try {
if (\$backup.DnsServers) {
\$ifIndex = (Get-NetAdapter -Name \$adapter).ifIndex
Set-DnsClientServerAddress -InterfaceIndex \$ifIndex -ServerAddresses \$backup.DnsServers -ErrorAction Stop
Write-Host "Restored DNS servers: \$($backup.DnsServers -join ', ')"
}
} catch {
Write-Warning "Failed to restore DNS: \$_"
}
# Restore Bluetooth service state
if (\$backup.BluetoothService.Exists -eq \$true) {
try {
\$svc = Get-Service -Name bthserv -ErrorAction SilentlyContinue
if (\$svc) {
if (\$backup.BluetoothService.Status -ne \$svc.Status) {
if (\$backup.BluetoothService.Status -eq 'Running') { Start-Service -Name bthserv -ErrorAction SilentlyContinue }
if (\$backup.BluetoothService.Status -eq 'Stopped') { Stop-Service -Name bthserv -Force -ErrorAction SilentlyContinue }
}
\$wmi = Get-WmiObject -Class Win32_Service -Filter "Name='bthserv'"
if (\$wmi -and \$backup.BluetoothService.StartType) {
\$wmi.ChangeStartMode(\$backup.BluetoothService.StartType) | Out-Null
Write-Host "Restored Bluetooth service start mode to \$($backup.BluetoothService.StartType)"
}
}
} catch {
Write-Warning "Failed to restore Bluetooth service state: \$_"
}
}
# Restore power plan
try {
if (\$backup.PowerPlanGuid) {
powercfg -setactive \$backup.PowerPlanGuid
Write-Host "Restored power plan GUID \$($backup.PowerPlanGuid)"
}
} catch {
Write-Warning "Failed to restore power plan: \$_"
}
Write-Host "Revert complete. Reboot recommended."
"@
$revertScript | Out-File -FilePath $revertPath -Encoding UTF8
Write-Host "`nRevert script created at: $revertPath" -ForegroundColor Green
# 11) Short ping test
Write-Host "`nRunning 20-second ping test to 8.8.8.8. Observe for spikes." -ForegroundColor Cyan
ping 8.8.8.8 -n 20
Write-Host "`nDone. Backup: $backupFile" -ForegroundColor Cyan
Write-Host "Revert script: $revertPath" -ForegroundColor Cyan
Write-Host "Reboot recommended to ensure all changes take effect." -ForegroundColor Yellow

How to execute the revert script safely

Location: the script and backup are stored in %USERPROFILE%\WiFiGamingBackup.

Option A — Run from File Explorer

  1. Press Win + R, paste:Code%USERPROFILE%\WiFiGamingBackup and press Enter.
  2. Right‑click the revert script Revert-WiFiGamingChanges_*.ps1Run with PowerShell.
  3. Confirm any prompts and reboot when finished.

Option B — Run from PowerShell ISE (recommended)

  1. Open PowerShell ISE as Administrator.
  2. File → Open → select the revert script from %USERPROFILE%\WiFiGamingBackup.
  3. Press Run Script (F5).
  4. Reboot after completion.

Option C — Run from an elevated PowerShell prompt

  1. Open Windows PowerShell as Administrator.
  2. Run:

powershell

Set-Location -Path "$env:USERPROFILE\WiFiGamingBackup"
.\Revert-WiFiGamingChanges_YYYYMMDD_HHMMSS.ps1
  1. Reboot when the script finishes.

What the revert script restores

  • Adapter advanced properties to their previous values.
  • Adapter DNS servers to the saved values.
  • Bluetooth service status and startup mode (if present).
  • The previously active power plan GUID.

Keep the backup JSON and revert script in %USERPROFILE%\WiFiGamingBackup until you’re confident the changes are permanent.

Edge hardware acceleration

Why enable it Hardware acceleration offloads video decoding and rendering to the GPU, reducing CPU load and improving frame stability for browser‑based cloud gaming. This can reduce stutter and improve perceived responsiveness.

How to enable in Edge

  1. Open Microsoft Edge.
  2. Go to Settings → System and performance.
  3. Toggle Use hardware acceleration when available to On.
  4. Restart Edge for the change to take effect.

Extra Edge tips

  • Enable secure DNS in Edge: Settings → Privacy, search, and services → Use secure DNS and choose Cloudflare or Google.
  • Disable unnecessary extensions and close background tabs while gaming.

Verify success and troubleshoot

  • Ping test: open an elevated PowerShell or Command Prompt and run:

powershell

ping 8.8.8.8 -t

Look for consistent low latency and no frequent spikes above ~50–100 ms.

  • Play test: run a short cloud gaming session and compare responsiveness to your wired baseline.
  • If spikes persist: temporarily disable Bluetooth to test whether the controller is the cause; try a USB‑Ethernet adapter or a short Ethernet run if possible; consider router changes (channel, QoS) if you can access it.

Final notes and safety

  • Driver variability: some adapter properties may not exist; the script skips missing items and records current values for rollback.
  • Bluetooth stop is optional and disabled by default to avoid disrupting other devices. Change the script variable only if you understand the impact.
  • Router changes (channel, width, QoS) are not modified by this script and require router access; they can further improve stability if needed.
  • Keep the backup and revert script in %USERPROFILE%\WiFiGamingBackup so you can restore original settings at any time.

Happy Gaming!

How to convert a Markdown file to Word document with PowerShell: A One-Line Solution

Overview

Need to convert Markdown files to Word documents quickly and efficiently? 

This one line PowerShell leverages the Word COM object to transform your .md files into professionally formatted .docx documents – no external tools or dependencies required.

The Solution

This script provides a complete Markdown-to-Word converter that handles all common formatting elements. Simply update the file path, paste into PowerShell, and execute.

$mdPath = "C:\Users\Documents\report.md"; if (-not (Test-Path $mdPath)) { Write-Error "❌ Markdown file not found at: $mdPath"; return }; $docPath = [System.IO.Path]::ChangeExtension($mdPath, ".docx"); function Process-InlineFormatting { param($range, $text); $position = 0; $segments = @(); while ($position -lt $text.Length) { if ($text.Substring($position) -match '^\*\*\*(.+?)\*\*\*') { $segments += @{Text = $matches[1]; Bold = $true; Italic = $true}; $position += $matches[0].Length } elseif ($text.Substring($position) -match '^\*\*(.+?)\*\*') { $segments += @{Text = $matches[1]; Bold = $true; Italic = $false}; $position += $matches[0].Length } elseif ($text.Substring($position) -match '^\*(.+?)\*') { $segments += @{Text = $matches[1]; Bold = $false; Italic = $true}; $position += $matches[0].Length } else { $nextAsterisk = $text.IndexOf('*', $position); if ($nextAsterisk -eq -1) { $segments += @{Text = $text.Substring($position); Bold = $false; Italic = $false}; break } else { $segments += @{Text = $text.Substring($position, $nextAsterisk - $position); Bold = $false; Italic = $false}; $position = $nextAsterisk } } }; foreach ($segment in $segments) { if ($segment.Text) { $range.Text = $segment.Text; $range.Font.Bold = $segment.Bold; $range.Font.Italic = $segment.Italic; $range.Collapse(0) } } }; try { $word = New-Object -ComObject Word.Application; $word.Visible = $false; $doc = $word.Documents.Add(); $lines = Get-Content $mdPath -Encoding UTF8; $wdCollapseEnd = 0; $wdBorderBottom = 4; $wdLineStyleSingle = 1; $wdColorBlack = 0; $wdLineWidth150pt = 6; foreach ($line in $lines) { $range = $doc.Content; $range.Collapse($wdCollapseEnd); if ($line -match '^\s*$') { $range.Text = "`r"; $range.Style = "Normal"; continue }; if ($line -match '^\s*[-]{3,}\s*$') { $range.Text = "`r"; $range.Style = "Normal"; $range.ParagraphFormat.Borders.Item($wdBorderBottom).LineStyle = $wdLineStyleSingle; $range.ParagraphFormat.Borders.Item($wdBorderBottom).Color = $wdColorBlack; $range.ParagraphFormat.Borders.Item($wdBorderBottom).LineWidth = $wdLineWidth150pt } elseif ($line -match '^(#{1,6})\s+(.+)') { $headerLevel = $matches[1].Length; $headerText = $matches[2]; Process-InlineFormatting -range $range -text $headerText; $range.Style = "Heading $headerLevel"; $range.Collapse($wdCollapseEnd); $range.Text = "`r" } elseif ($line -match '^\s*[-*+]\s+(.+)') { $listText = $matches[1]; Process-InlineFormatting -range $range -text $listText; $range.Style = "List Bullet"; $range.Collapse($wdCollapseEnd); $range.Text = "`r" } elseif ($line -match '^\s*\d+\.\s+(.+)') { $listText = $matches[1]; Process-InlineFormatting -range $range -text $listText; $range.Style = "List Number"; $range.Collapse($wdCollapseEnd); $range.Text = "`r" } else { Process-InlineFormatting -range $range -text $line; $range.Style = "Normal"; $range.Collapse($wdCollapseEnd); $range.Text = "`r" } }; $doc.SaveAs([ref] $docPath); $doc.Close(); $word.Quit(); [System.Runtime.Interopservices.Marshal]::ReleaseComObject($word) | Out-Null; [System.GC]::Collect(); [System.GC]::WaitForPendingFinalizers(); Write-Host "✅ Markdown converted to Word document at $docPath" } catch { Write-Error "❌ An error occurred: $_"; if ($doc) { $doc.Close($false) }; if ($word) { $word.Quit(); [System.Runtime.Interopservices.Marshal]::ReleaseComObject($word) | Out-Null } }

How to Use

Step 1: Update the file path at the beginning of the script

$mdPath = "C:\Users\Documents\report.md"

Step 2: Copy the entire one-line script above

Step 3: Paste into PowerShell and press Enter

Step 4: Your Word document will be created in the same directory with a .docx extension

Supported Markdown Features

The script converts the following Markdown elements to their Word equivalents:

Headers

markdownDownloadCopy code# Heading 1
## Heading 2
### Heading 3

Converts to Word’s built-in Heading 1, 2, 3 styles (up to Heading 6)

Text Formatting

  • Bold: **text** → text
  • Italic: *text* → text
  • Bold + Italic: ***text*** → text

Lists

Bullet Lists:

markdownDownloadCopy code- Item one
* Item two
+ Item three

Numbered Lists:

markdownDownloadCopy code1. First item
2. Second item
3. Third item

Horizontal Rules

markdownDownloadCopy code---

Creates a horizontal line with bottom border formatting

Special Characters

UTF-8 encoding support ensures proper handling of international characters, accents, and special symbols.

How It Works

Inline Formatting Parser

The script includes a custom function that processes inline formatting by:

  1. Parsing text character-by-character
  2. Identifying markdown formatting markers (******)
  3. Extracting the text within markers
  4. Applying appropriate Word formatting (Bold, Italic, or both)
  5. Removing the markdown syntax

Document Processing

The script:

  • Reads the markdown file with UTF-8 encoding
  • Processes each line sequentially
  • Applies regex pattern matching to identify markdown elements
  • Uses Word COM object methods to apply appropriate styles
  • Handles blank lines to preserve document spacing
  • Properly manages Word styles to prevent formatting conflicts

COM Object Management

The script properly initializes and cleans up Word COM objects to prevent memory leaks and ensure smooth execution.

Key Features

✅ No External Dependencies – Uses built-in PowerShell and Word COM automation
✅ UTF-8 Support – Handles international characters correctly
✅ Inline Formatting – Processes bold and italic within any text element
✅ Professional Output – Uses Word’s native styles for consistent formatting
✅ One-Line Execution – Paste and run without saving to a file
✅ Error Handling – Includes try-catch blocks for robust execution
✅ Memory Management – Properly releases COM objects after execution

Use Cases

This script is perfect for:

  • Documentation Automation – Convert markdown documentation to Word format for distribution
  • Report Generation – Transform markdown reports into professional Word documents
  • Content Migration – Batch convert markdown files to Word format
  • Workflow Integration – Incorporate into larger PowerShell automation scripts
  • Quick Conversions – Ad-hoc conversion without installing additional tools

Customization Options

Change Output Location

Modify the $docPath variable to specify a different output location:

$docPath = "C:\Output\CustomName.docx"

Make Word Visible

Set $word.Visible = $true to watch the conversion process in real-time

Adjust Border Styling

Modify the Word constants to customize horizontal rule appearance:

$wdLineWidth150pt = 6  # Change border thickness
$wdColorBlack = 0 # Change border color

Requirements

  • Windows operating system
  • Microsoft Word installed
  • PowerShell 5.1 or later
  • Appropriate permissions to create COM objects

Technical Details

Word COM Constants Used:

  • wdCollapseEnd = 0 – Collapse range to end position
  • wdBorderBottom = 4 – Bottom border identifier
  • wdLineStyleSingle = 1 – Single line style
  • wdColorBlack = 0 – Black color value
  • wdLineWidth150pt = 6 – Border width (1.5pt)

Regex Patterns:

  • Headers: '^(#{1,6})\s+(.+)' – Requires space after #
  • Bullet Lists: '^\s*[-*+]\s+(.+)' – Supports -, *, + markers
  • Numbered Lists: '^\s*\d+\.\s+(.+)' – Matches numbered items
  • Horizontal Rules: '^\s*[-]{3,}\s*$' – Three or more hyphens

Conclusion

This PowerShell one-liner provides a powerful, dependency-free solution for converting Markdown to Word documents. Whether you’re automating documentation workflows or need a quick conversion tool, this script delivers professional results with minimal setup.

Simply update the file path, paste the script, and let PowerShell handle the rest!

Building a Scalable 2D Game Scene Architecture: From Back to Front

Creating a clean, scalable scene architecture for a 2D game is more than just organizing visuals—it’s about building a system that supports gameplay, UI, effects, and camera logic in a way that’s intuitive and future-proof. In this post, we’ll walk through a layered architecture that separates concerns, supports depth-based gameplay, and keeps your UI crisp and your effects polished.

Whether you’re building a vertical shooter, a platformer, or a retro arcade game, this structure gives you the flexibility to scale without chaos.

🧱 Scene Graph Overview

At the core is root_scene, which contains all visual and logical layers. These layers are organized from background to foreground, with clear roles and transformation rules.

root_scene
├── game_group                            # Camera-controlled gameplay container
│   ├── hidden_group                     # Off-screen/inactive entities (object pooling)
│   ├── background_group                 # Default background layer + depth container
│   │   ├── background_bottom_group      # Farthest background visuals (sky, base)
│   │   ├── background_mid_group         # Parallax mid-layers, distant FX
│   │   ├── background_top_group         # Closest background visuals
│   ├── objects_group                    # Default gameplay layer + depth container
│   │   ├── objects_depth_bottom_group   # Farthest gameplay entities
│   │   ├── objects_depth_mid_group      # Primary gameplay layer (player, pickups)
│   │   ├── objects_depth_top_group      # Foreground gameplay entities
│   ├── foreground_group                 # Foreground visuals + depth container
│   │   ├── foreground_bottom_group      # Farthest foreground elements
│   │   ├── foreground_mid_group         # Mid-range foreground visuals
│   │   ├── foreground_top_group         # Closest foreground overlays
│   ├── visual_fx_group                  # Explosions, particles, transient visuals
│
├── hud_group                            # Score, gauges, indicators (screen-anchored)
├── menu_group                           # Title screen, credits (non-blocking UI)
├── modal_group                          # Pause, game over, dialogs (blocking overlays)
├── debug_group                          # Dev-only overlays, performance HUD
├── screen_fx_group                      # CRT shader, bloom, vignette (post-processing)
  

🧠 Layer Roles & Camera Behavior

Each layer has a defined purpose and relationship with the camera. Gameplay and visual layers move with the camera, while UI and post-processing layers remain fixed or apply globally. Note that background and foreground groups can also be used in menu layers along with menu group.

LayerPurposeTransforms with Camera
game_groupMaster container for gameplay layers✅ Yes
hidden_groupObject pooling, inactive/off-screen entities✅ Yes
background_groupDefault background layer✅ Yes
background_bottom_groupFarthest background visuals (sky, base)✅ Yes
background_mid_groupParallax mid-layers, distant FX✅ Yes
background_top_groupClosest background visuals✅ Yes
objects_groupDefault gameplay layer✅ Yes
objects_depth_bottom_groupFarthest gameplay entities✅ Yes
objects_depth_mid_groupCore gameplay layer (player, enemies, pickups)✅ Yes
objects_depth_top_groupForeground gameplay entities✅ Yes
foreground_groupDefault foreground layer✅ Yes
foreground_bottom_groupFarthest foreground visuals✅ Yes
foreground_mid_groupMid-range foreground visuals✅ Yes
foreground_top_groupClosest foreground overlays✅ Yes
visual_fx_groupExplosions, particles, screen shake✅ Yes
hud_groupScore, gauges, indicators❌ No
menu_groupTitle screen, credits❌ No
modal_groupPause, game over, dialogs❌ No
debug_groupDev overlays, performance HUD❌ No
screen_fx_groupPost-processing shaders (CRT, bloom, vignette)❌ No (global)

🧰 API Naming Conventions

To keep things clean and predictable, each layer has dedicated adders and getters. This ensures encapsulation and avoids direct manipulation of scene graph internals.

🔧 Adders

python

add_to_hidden_group(obj)
add_to_background_group(obj)
add_to_background_bottom_group(obj)
add_to_background_mid_group(obj)
add_to_background_top_group(obj)

add_to_objects_group(obj)
add_to_objects_depth_bottom_group(obj)
add_to_objects_depth_mid_group(obj)
add_to_objects_depth_top_group(obj)

add_to_foreground_group(obj)
add_to_foreground_bottom_group(obj)
add_to_foreground_mid_group(obj)
add_to_foreground_top_group(obj)

add_to_visual_fx_group(obj)
add_to_hud_group(obj)
add_to_menu_group(obj)
add_to_modal_group(obj)
add_to_debug_group(obj)
add_to_screen_fx_group(obj)
  

🔍 Getters

python

get_hidden_group()
get_background_group()
get_background_bottom_group()
get_background_mid_group()
get_background_top_group()

get_objects_group()
get_objects_depth_bottom_group()
get_objects_depth_mid_group()
get_objects_depth_top_group()

get_foreground_group()
get_foreground_bottom_group()
get_foreground_mid_group()
get_foreground_top_group()

get_visual_fx_group()
get_hud_group()
get_menu_group()
get_modal_group()
get_debug_group()
get_screen_fx_group()
  

📏 Ownership & Layering Rules

To maintain clarity and prevent misuse, each type of entity has a designated home:

  • Gameplay entitiesobjects_group or one of its depth layers
  • Background visualsbackground_group or its depth layers
  • Foreground visualsforeground_group or its depth layers
  • HUD elementshud_group
  • Menusmenu_group
  • Blocking overlaysmodal_group
  • Debug toolsdebug_group only
  • Visual effectsvisual_fx_group
  • Post-processing shadersscreen_fx_group
  • Camera transformations → applied only to game_group and its children

🚫 Layering Constraints

To avoid rendering chaos and maintain performance:

  • ❌ No toFront() calls in gameplay layers
  • ✅ UI systems may adjust local order within their own group
  • ✅ Depth layers maintain internal z-ordering

✅ Benefits of This Architecture

  • Clear separation of concerns: Each layer has a distinct visual and logical role
  • Scalable and maintainable: Easy to audit, extend, and debug
  • Camera-friendly: game_group isolates gameplay transformations from UI
  • Depth flexibility: objects_group, background_group, and foreground_group support layered interactions
  • UI integrity: HUD and modals remain crisp and unaffected by zoom/shake
  • Post-processing polish: screen_fx_group applies final visual effects globally

🧪 Final Thoughts

This architecture isn’t just a technical blueprint—it’s a philosophy of clarity. By separating gameplay, background, foreground, UI, and effects into well-defined layers, you empower your team to build faster, debug smarter, and scale confidently.

If you’re working on a game and want help adapting this structure to your engine or genre, I’d love to collaborate. Let’s build something beautiful.

How to Fix the GC IPL Error in Dolphin When Using RetroBat

If you’ve ever tried launching a GameCube game through Dolphin and been greeted with the dreaded “GC IPL file could not be found” error, you’re not alone. This issue can be frustrating, especially when everything else seems to be set up correctly. But don’t worry—there’s a simple fix, and we’ll walk you through it.

🧩 What Causes the GC IPL Error?

The error typically stems from a missing or incorrect BIOS file (also known as the IPL file) required for the GameCube boot animation. While the game itself may be perfectly fine, Dolphin attempts to load the BIOS sequence before launching the game—and if it can’t find the right file, it throws an error.

✅ Fixing the Error in Dolphin (Standalone)

If you’re running Dolphin directly (not through RetroBat), you can bypass the BIOS boot sequence entirely by tweaking a simple setting:

  1. Locate your dolphin.ini configuration file.
  2. Open it in a text editor.
  3. Find the line that says SkipIPL.
  4. Set it to True.

ini

[Core]
SkipIPL = True

This tells Dolphin to skip the BIOS animation and jump straight into the game—no IPL file needed.

🔄 Fixing the Error in Dolphin via RetroBat

If you’re using RetroBat as your frontend, the fix is slightly different. RetroBat tends to overwrite Dolphin’s configuration files each time you launch a game, so editing dolphin.ini manually won’t stick.

Instead, you need to configure RetroBat itself to skip the BIOS:

  1. Launch RetroBat and press Start to open the Main Menu.
  2. Navigate to: Game Settings > Per System Advanced Configuration
  3. Select the console you’re working with (e.g., GameCube).
  4. Go to: Emulation > Skip Bios
  5. Set it to Yes.

This ensures that RetroBat tells Dolphin to skip the IPL sequence every time, avoiding the error altogether.

🎮 Final Thoughts

The GC IPL error might seem like a showstopper, but it’s really just a BIOS boot hiccup. Whether you’re using Dolphin standalone or through RetroBat, skipping the IPL sequence is a quick and effective workaround. Now you can get back to what matters—playing your favorite GameCube classics without interruption.

Got other emulation quirks you’re trying to solve? Drop them in the comments or reach out—I’m always up for a good retro tech fix.

Comprehensive Guide to Helping an Ai Coding Agent Identify and Avoid Common Coding Bad Practices

Introduction

In large projects, subtle anti-patterns can slip through reviews—like importing modules mid-file or conditionally. These non-standard import placements obscure dependencies, make static analysis unreliable, and lead to unpredictable runtime errors. This web article dives into that practice, outlines a broader set of coding bad practices, and even provides a ready-to-use AI coding agent prompt to catch every issue across your codebase.

What Is Non-Standard Import Placement?

Imports or require statements buried inside functions, conditional branches, or midway through a file violate expectations of where dependencies live. Best practices and most style guides mandate that:

  • All imports sit at the top of the file, immediately after any module docstring or comments.
  • Conditional or lazy loading only happens with clear justification and documentation.

When imports are scattered:

  1. Static analysis tools can’t reliably determine your project’s dependency graph.
  2. Developers hunting for missing or outdated modules lose time tracing hidden import logic.
  3. You risk circular dependencies, initialization bugs, or runtime surprises.

A Broader List of Coding Bad Practices

Below is a table of widespread anti-patterns—some classic hygiene issues and others that modern AI agents might inject or overlook:

Bad PracticeDescription
Spaghetti CodeCode with no clear structure making maintenance difficult.
Hardcoding ValuesEmbedding constants directly instead of using config or constants.
Magic Numbers/StringsUsing unexplained literals instead of named constants.
Global State AbuseOverusing global variables causing unpredictable side effects.
Poor Naming ConventionsUsing vague or misleading variable and function names.
Lack of ModularityWriting large monolithic blocks instead of reusable functions.
Copy-Paste ProgrammingDuplicating code rather than abstracting shared logic.
No Error HandlingIgnoring exceptions or failing to validate inputs.
OverengineeringAdding unnecessary complexity or abstraction.
Under-documentationFailing to comment or explain non-obvious logic.
Tight CouplingMaking modules overly dependent on each other.
Ignoring Style GuidesNot following language-specific conventions or style guides.
Dead CodeLeaving unused or unreachable code paths in the codebase.
Inconsistent FormattingMixing indentation styles or inconsistent code layout.
Not Using Version Control ProperlyCommitting broken code, poor commit messages, ignoring branching.
Non-standard Import PlacementPlacing imports mid-file or conditionally instead of at the top.
Missing Security ChecksOmitting authentication, authorization, or input sanitization.
Inefficient AlgorithmsUsing suboptimal logic that hurts performance.
Hallucinated DependenciesReferencing non-existent libraries or methods from AI suggestions.
Incomplete Code GenerationLeaving functions or loops unfinished due to AI cutoffs.
Prompt-biased SolutionsGenerating code that only fits the prompt and fails general cases.
Missing Corner CasesOverlooking edge cases and error conditions in logic.
Incorrect Error MessagesProviding vague or misleading error feedback to users.
Logging Sensitive DataWriting confidential information to logs without sanitization.
Violating SOLID PrinciplesBreaking single responsibility or open/closed design rules.
Race ConditionsFailing to handle concurrency leading to unpredictable bugs.

Crafting an AI Coding Agent Prompt

To ensure an AI auditor doesn’t skip files, ignore edge cases, or take shortcuts, use the following prompt. It instructs the agent to comprehensively scan every line, record each finding, and tally occurrences of every bad practice.

## Prompt

You are an expert AI code auditor. Your mission is to exhaustively scan every file and line of the codebase and uncover all instances of known bad practices. Do not skip or shortcut any part of the project, even if the code is large or complex. Report every finding with precise details and clear remediation guidance.

## Scope
- Analyze every source file, configuration, script, and module.
- Treat all code as in-scope; do not assume any file is irrelevant.

## Bad Practices to Detect
- Spaghetti Code
- Hardcoding Values
- Magic Numbers/Strings
- Global State Abuse
- Poor Naming Conventions
- Lack of Modularity
- Copy-Paste Programming
- No Error Handling
- Overengineering
- Under-documentation
- Tight Coupling
- Ignoring Style Guides
- Dead Code
- Inconsistent Formatting
- Improper Version Control Usage
- Non-standard Import Placement
- Missing Security Checks
- Inefficient Algorithms
- Hallucinated Dependencies
- Incomplete Code Generation
- Prompt-biased Solutions
- Missing Corner Cases
- Incorrect Error Messages
- Logging Sensitive Data
- Violating SOLID Principles
- Race Conditions

## Analysis Instructions
1. Traverse the entire directory tree and open every file.
2. Inspect every line—do not skip blank or comment lines.
3. Identify code snippets matching any bad practice.
4. For each instance, document:
   - File path
   - Line number(s)
   - Exact snippet
   - Bad practice name
   - Explanation of why it’s problematic
   - Suggested refactoring

5. Keep a running tally of occurrences per bad practice.

## Output Requirements
- Use Markdown with a section per file.
- Subheadings for each issue.
- End with a summary table listing each bad practice and its total count.
- If the repo is too large, process in ordered batches (e.g., by folder), confirming coverage before proceeding.
- Do not conclude until every file has been reviewed.

Begin the full project audit now, acknowledging you will not take shortcuts.

Next Steps

  • Integrate this prompt into your AI workflow or CI pipeline.
  • Pair it with linters and static analyzers (ESLint, Flake8, Prettier) for automated, real-time checks.
  • Enforce code review policies that catch both human and AI-introduced anti-patterns.

By combining clear style guidelines, automated linting, and an uncompromising AI audit prompt, you’ll dramatically improve code quality, maintainability, and security—project-wide.

Fixing Emulators in RetroBat: Quick Install Guide

Sometimes, quite often actually 🙄, things get corrupted in RetroBat. I’m not knocking it, it’s an absolutelly fantastic emulation frontend and thinkering is just part of the retro games emulation experience. But, yeah, with thinkering. unfortunately things get broke. This includes the emulators themselves.

A quick and dirty way of performing a fresh install of an emulator in RetroBat is to simply delete the specific emulator folder. For example to remove Duckstation, navigate to RetroBat\emulators and then delete Duckstation. To reinstall it then simply open RetroBat enter RETROBAT emulator list and then reinstall.

Side Note:

Instead of Duckstation you can also use Swanstation in Retroarch.

A description of the two is below:

🦆 DuckStation

  • Type: Standalone emulator
  • Focus: High compatibility, speed, and long-term maintainability
  • Platform: Available on Windows, Linux, Android, and macOS
  • Features:
    • Modern UI
    • Save states, widescreen hacks, texture filtering
    • Supports achievements (RetroAchievements)
  • Development: Actively maintained by its original developer
  • Best For: Users who want a full-featured, standalone PS1 emulator with a modern interface

🦢 SwanStation

  • Type: Libretro core (used within RetroArch)
  • Origin: Fork of DuckStation, created due to licensing and distribution disagreements
  • Focus: Same as DuckStation—playability and performance—but within the RetroArch ecosystem
  • Features:
    • Nearly identical emulation to DuckStation
    • Integrated into RetroArch’s unified interface
  • Limitations:
    • May lag behind DuckStation in updates
    • Slightly less customizable outside of RetroArch
  • Best For: Users who prefer RetroArch’s all-in-one emulator interface

How to fix a noisy GPU fan

If you have an old GPU and you have noticed it is creating more noise now than when it was first installed, it may be because the bearing is no longer sufficiently lubricated.

A new fan off EBay, or new to you, might not be a sound investment. This is true assuming you can even find a replacement. Luckily most GPU fans can be serviced.

To Lubricate the Fan Bearing:

Remove the GPU from your computer.

Spin the fan manually. If it is not mostly silent, then there is a problem with the bearing. More than likely, there is just insufficient lubricant.

Carefully peel back the sticker on the top of the fan to expose the bearing. If access to the bearing is not provided on the top of the fan, then remove the fan from the GPU housing. Peel back the sticker on the back of the fan. If you see a yellowish mark on the inside of the sticker where the bearing is, this confirms the lubricant has leaked somewhat. If access to the bearing is not provided at the bottom of the fan either then very carefully try to remove the blade.

Once the bearing is exposed apply a small drop of light machine oil or sewing machine oil to the bearing. Singer oil is a good choice.

Rotate the fan manually to work the oil into the bearing.

Replace the sticker or use a small piece of tape to cover the bearing.

Before reinstalling the GPU, you might consider also applying fresh thermal paste to the GPU processor. This will help keep temperatures down, giving the fan less work to do.

How to fix no audio issues with RetroArch on Windows 10 or 11

To troubleshoot a sound issue with RetroArch, follow these steps:

  1. Check that DirectX 9 is installed on the PC. DirectX 9 is not part of Windows 11 by default and if you are running retroarch via an external drive, i.e. usb key, on a new machine it may not have the necessary runtime installed. It can be downloaded here: LINK Note: when the installer is run it will ask what folder to use. This folder is just a temp folder, essentially it is asking where the contents should be extracted. Once the process is finised navigate to the folder you chose and run the DXSETUP.exe file. Once this file has run you can delete the temp folder.
  2. Check if the RetroArch program is muted by pressing random keys on your computer. If it’s muted, go to Settings > Audio and turn off Mute.
  3. If the sound issue persists, try setting the default audio driver to Wasapi and unchecking WASAPI Exclusive Mode and WASAPI Float Format.
  4. If the volume mixer is the culprit, check if it’s listed on the volume mixer and check if RetroArch was set to low volume or muted. If it’s not listed, try downloading a new copy of RetroArch from their website and extracting it into a new directory.
  5. Restart after trying the solutions above.

Organizing ROM Files: How to Safely Move and Sort by Region

Typically there is a pattern to rom file names indicating what region the files belong to. For example it might explicitly state “(USA)” in the file name. However this alone cannot be taken as confirmation that “USA” means the game is from the USA region, USA could simply be part of the game title, e.g. “Daytona USA” etc. Japanese roms often end with the letter “j” but again this could just be part of the name of the game.

So how can one go about cleaning a rom folder without destroying these roms in the process?

You can still use these string elements to move files from one folder to another. The code below will not delete the files it will just move them en masse. If you have found a game was wrongly moved you can always return it.

Note: Always backup files and folders before you manipulate them programmatically.

The code below will need to be updated to reference your specific rom folder and where you want the files to move as well as the character string you are targeting i.e. *j.zip

@echo off

REM Examples file types:
REM Move Japanese files target is *j.zip
REM Move USA files target is *u.zip
REM Move USA files target is *(USA).zip
set target="*j.zip"

set source_folder="C:\roms\"
set destination_folder="C:\roms\_j_roms"

if not exist %destination_folder% (
    md %destination_folder%
)

move %source_folder%%target% %destination_folder%

echo Files with the extension %target% have been moved to %destination_folder%