Alec Harrison and Brian Gorman return from a short break to talk all things certifications, Copilot, and the curious evolution of learning with AI. Alec shares his experience taking Microsoft’s new Applied Skills exams for Copilot Studio, while Brian gives some veteran insight into two decades of Microsoft certifications and how the new role-based system compares.
They debate whether AI tools are replacing junior engineers, discuss what makes modular Infrastructure as Code essential, and riff on the future of “speech-to-IaC” — where voice meets automation. Plus, Brian shares his own upcoming video course and gives pragmatic advice for anyone chasing their next cert.
Another month is down and November is here! 2025 is nearing the end, have you achieved all your goals this year? You still have 60 days to go! I hope you achieve everything you set out to do!
Thanks to everyone who picked up the first batch of my Problem Solving course! So far the feedback has been great and I'm actively recording all the remaining lectures. This week I'm also starting the 1on1 live calls, it should be fun! If you bought the course and you haven’t scheduled your call yet check your email inbox for the link.
I will be opening up more slots soon after I get through this first batch of calls, stay tuned!
It's rare to have games find success outside of Steam. You have only a handful of examples like Minecraft, League of Legends, Genshin Impact, etc. Then others found massive success during an early release and only went onto Steam after they were super polished, like Factorio and Prison Architect. One of the biggest that is still not on Steam is Escape from Tarkov, it has been selling millions of copies since 2017 just on their own website. (By the way this is actually a game made with Unity! A lot of people don’t know that because they still think Unity cannot produce AAA-level visuals, but this game shows it can be done)
However there are some devs that only buy games on Steam and if it's not on Steam then it doesn't exist for them. This is actually a nice strategy that can be a great opportunity for indie devs.
One developer did just that with the recently released Escape from Duckov. The name is obviously a clear reference and the gameplay is also similar in various ways while also being unique and having its own identity. Partly due to the similarity to that massively successfuly game, and partly due to the fact that this one is genuinely good, it is selling like crazy with 2 million copies sold in just 2 weeks!
It is a top down game with lots of guns and attachments, a huge world, lots of loot, although it is PvE instead of Escape from Tarkov's PvP. The game is also insanely well polished, this is one of those games that it can be worth it to get it just to study how well polished it is. The movement feels satisfying, the line of sight looks great, the shooting looks, sounds, and feels great.
And of course the game doesn't take itself too seriously, the ducks give some levity to what is usually a very hardcode genre, and I think that's also part of the reason this game is finding success, a lot of people want to play Escape from Tarkov but don't want to deal with the super hardcode difficulty of losing everything when you die.
The team spoke to Game Developer a bit about the development of the game, how it started as a 4 man team and grew from there.
I really think more indies should try this strategy. Find a game that is super popular but not on Steam, and make your own version of it with your own unique twist. Indie game dev is already insanely risky so finding ways to minimize that risk is super important.
Affiliate
FREE Synty Pack
The Publisher of the Week this time is Synty Studios, I’m guessing you know them already.
Get the FREE POLYGON Nightclubs which is a great pack, perfect for making a Nightclub simulator game!
Get it HERE and use coupon SYNTY2025 at checkout to get it for FREE!
Game Dev
Unity 20th Anniversary Game Jam!
Unity is turning 20 this year! It's crazy to think how it started in 2005 after they made a game called GooBall and it flopped, in doing so they realised that the game engine was actually more valuable than the game they made.
To celebrate this milestone they are having an Anniversary Game Jam! It is running next weekend for 48 hours with the theme being announced on November 7th, 2025 at 5pm UTC.
Also as part of this celebration they have updated a bunch of assets from the past 20 years to Unity 6 that you can download, so if you've been using Unity for a long time you might recognize some.
You can join either solo or in a team, they have their official Discord if you want to find someone.
I will be joining this game jam! It's been a while since I joined one so this should be fun. I'll be livestreaming the development so stay tuned next week!
Robotics has been getting a massive boost these past few years. It wasn't that long ago that Boston Dynamics was making their first bipedal robots, and now there are loads of those, all of them can walk fine and do all kinds of tasks.
Now the next step is to finally make them useful and available for regular people, it seems that moment is finally here with NEO that you can order today!
Except not really... You can pay today but deliveries start in 2026. And more importantly is how the video seems super cool with a very capable robot doing all kinds of tasks, but in reality there is another video that more clearly shows what is actually real and the reality is all those complex tasks are currently teleoperated. Meaning someone in a VR headset is controlling the robot, it is not controlling itself. The goal is to use AI to make it autonomous, but it needs data to learn so for now it’s all just human operated.
Meaning we still have a ways to go until we reach the stage where we have a commercially available humanoid robot that is capable of doing all kinds of tasks humans can do autonomously.
But still in terms of tech this is impressive! The robot has very good dexterity, it is using a pulley system for the fingers like some other robots that give it extremely precise control. And like I mentioned in the beginning it appears regular walking is now basically a solved problem. The visual is also interesting, it is wearing a knitted sweater which does make it look more friendly.
Perhaps by the time it actually launches in 2026 it will be somewhat capable.
I love robotics and I'm definitely counting down the time until a sci-fi robot becomes reality. Having something like Rosie from the Jetsons would be super impressive, and it's only a matter of time until it does happen.
Blender is a very complex program that requires lots of hotkeys to use effectively, but what if you just used your hands? It turns out that's actually possible!
Some artists have built really impressive models on their phone using nothing but their fingers.
It starts from a cube and ends up with a proper low poly face. It's even textured! Impressive stuff!
I have no art skills and my 3D modelling skills are insanely basic so I'm always amazed when I see how a proper artist can build something awesome so quickly. I guess an artist would feel the same way watching me code up a complex system in a few hours?
It's amazing how behind even simple products there is an insane amount of knowledge required to build it. I love the Newton quote "If I have seen further, it is by standing on the shoulders of giants"
Get Rewards by Sending the Game Dev Report to a friend!
(please don’t try to cheat the system with temp emails, it won’t work, just makes it annoying for me to validate)
Mocking is a superpower in modern unit testing, but like any power, it can be overused. Used wisely, mocks make tests lightning-fast and clean. Used recklessly, they create an illusion of stability while real bugs hide in production.
In this post, we’ll explore when to use mocking in unit testing, when it hurts, and how to build a healthy test suite that blends both isolation and reality.
Mocking shines when your code interacts with anything outside your control:
External APIs or microservices
File systems, databases, or queues
Time, randomness, or system state
Legacy dependencies that are expensive or slow to instantiate
Mocks give you deterministic, isolated, repeatable tests – perfect for agile teams and CI pipelines.
Example (.NET / MSTest)
[TestMethod]
public void Should_Send_Notification_Without_Hitting_Real_Service()
{
var email = Isolate.Fake.Instance<IEmailService>();
Isolate.WhenCalled(() => email.Send("dev@typemock.com"))
.WillReturn(true);
var notifier = new Notifier(email);
Assert.IsTrue(notifier.Notify("dev@typemock.com"));
}
This test runs instantly, without any real email traffic. You’ve isolated the logic that matters and ignored the infrastructure that doesn’t.
Example (C++ / Isolator++)
TEST(Legacy, Should_Process_Data_Without_Hitting_Database)
{
Isolator a;
auto db = a.Fake.Instance<IDatabase>();
a.CallTo(db->SaveRecord()).WillReturn(true);
DataProcessor processor{db};
ASSERT_TRUE(processor.Process());
}
Here the fake database mimics expected behavior, letting you verify logic without connecting to the real DB.
When Not to Mock
If everything in your system is mocked, you’re not testing reality, you’re just validating assumptions. Mocks hide bugs when used in integration scenarios.
Avoid mocks when:
You’re testing data integrity or schema mapping
You need to verify serialization, configuration, or endpoints
⚠️ Over-mocking leads to “green builds” that fail in production.
The Rule of Three
A simple way to decide whether to mock:
Is it slow? → Mock it.
Is it external? → Mock it.
Is it internal and stable? → Test it directly.
This keeps your suite fast without faking the world.
Best Practices for When to Use Mocking in Unit Testing
✅ Keep one fake per test. ✅ Name fakes clearly (fakeGateway, mockRepo). ✅ Avoid mocking constructors or statics unless necessary. ✅ Use WillDoInstead() only when you need custom behavior. ✅ Add one integration test per module to validate the real flow. ✅ Use Isolator Insights to confirm mock behavior and track faked method calls visually.
Use Isolator Insights to Visualize Your Mocks
Even with the best practices above, it can be hard to see what’s really happening inside your tests. That’s where Typemock Isolator Insights comes in.
Insights gives you a real-time view of:
Which methods and objects were faked
How your mocks interact during test execution
Where setup or verification might be missing
You can instantly trace your test flow, verify mock behavior, and debug unexpected calls, all without adding extra logging.
💡 Use Insights whenever a test fails or behaves unexpectedly. It helps you confirm whether your mocks are configured correctly or if the issue lies in your real code.
How Typemock Helps
Most frameworks stop where your real problems start – sealed, static, or private methods, or deep legacy C++ code. That’s where Typemock excels.
Typemock Isolator for .NET lets you fake anything – no refactoring required.
Isolator++ for C++ isolates legacy code and static functions with a simple, modern API.
Both integrate smoothly with your CI and test frameworks.
Mock dependencies that are slow, external, or non-deterministic. Avoid mocking your own logic.
Is mocking the same in .NET and C++?
Yes, the concept is the same. Typemock provides native APIs for each language.
Can I mock private or static methods?
Absolutely. Typemock Isolator and Isolator++ are designed to do exactly that safely.
Does mocking replace integration tests?
No. Mocking isolates units of logic; you still need integration tests for full system confidence.
Conclusion
Mocking isn’t about avoiding the real world – it’s about controlling it while you verify your logic. A balanced testing strategy combines mock-based unit tests with selective integration tests, keeping your builds fast and reliable.
Use mocks wisely, and your tests will be your safety net – not your blindfold.
When building Hotkey Typer, a Windows Forms app that types predefined snippets via hotkey, I discovered the power of GitHub Copilot's new custom agents feature. What started as a simple refactoring request turned into an eye-opening demonstration of how specialized AI agents can dramatically improve code quality and implementation speed.
What Are Custom Agents?
Custom agents are specialized versions of GitHub Copilot coding agent that you can tailor to your unique workflows, coding conventions, and use cases. Instead of repeatedly providing the same instructions and context, custom agents allow you to define specialized agents that act like tailored teammates—following your standards, using the right tools, and implementing team-specific practices.
Think of custom agents as expert consultants embedded directly in your development workflow. They're defined using simple Markdown files (called agent profiles) that specify:
Name and description: What the agent specializes in
Custom prompts: Detailed instructions that define the agent's behavior and expertise
Tools: Specific tools the agent can access (optional)
MCP servers: Model Context Protocol server configurations for enhancing agents
Where You Can Use Custom Agents
Once created, custom agents are available wherever you use Copilot coding agent:
GitHub.com: In the agents tab, panels, issue assignments, and pull requests
Visual Studio Code: As chat modes in the editor
GitHub Copilot CLI: Command-line interactions
Each interaction maintains consistency based on the agent's defined profile, ensuring targeted support tailored to your specific needs.
Where to Configure Custom Agents
You can define agent profiles at different levels:
Repository level: Place .md files in .github/agents/ for project-specific agents
Organization level: For broader team availability
Enterprise level: For company-wide standardization
The agent profile format is straightforward:
---
name: readme-creator
description: Agent specializing in creating and improving README files
---
You are a documentation specialist focused on README files. Your scope is limited to
README files or other related documentation files only - do not modify or analyze code files.
Focus on the following instructions:
- Create and update README.md files with clear project descriptions
- Structure README sections logically: overview, installation, usage, contributing
- Write scannable content with proper headings and formatting
- Add appropriate badges, links, and navigation elements
Discovering the WinForms Expert Agent
The real power of custom agents became apparent when I discovered the awesome-copilot repository, a curated collection of custom agents maintained by GitHub. This repository contains specialized agents for various frameworks and technologies, including C#, WinForms, arm Migration, Dynatrace, LaunchDarkly, Neon, PagerDuty, and more!
Among these gems, I found the WinForms Expert agent—a sophisticated agent specifically designed for .NET Windows Forms development. This agent encodes decades of WinForms best practices into a single, reusable profile.
What Makes the WinForms Expert Special?
The WinForms Expert agent is a comprehensive guide that understands the nuanced challenges of WinForms development. It addresses several critical areas:
Designer Code vs. Regular Code: One of WinForms' unique challenges is managing two distinct code contexts—designer-generated serialization code and modern business logic. The agent knows exactly which C# features are allowed in each context.
Modern .NET Patterns: The agent is fully updated for .NET 8-10, including:
MVVM data binding patterns with the Community Toolkit
Async/await patterns with proper InvokeAsync overload selection
DarkMode support and high-DPI awareness
Nullable reference types (NRTs) in the right places
Layout Best Practices: Comprehensive guidance on using TableLayoutPanel and FlowLayoutPanel for responsive, DPI-aware layouts that work across different screen sizes and scaling factors.
CodeDOM Serialization Management: Critical rules for property serialization in the WinForms designer, preventing common pitfalls with [DefaultValue] attributes and ShouldSerialize*() methods.
Exception Handling: Proper patterns for async event handlers and application-level exception handling to prevent process crashes.
The agent's instructions are remarkably detailed, covering everything from control naming conventions to accessibility requirements. It's like having a senior WinForms architect reviewing every line of code.
Adding the WinForms Expert to My Repository
Adding a custom agent from the awesome-copilot repository is remarkably simple:
Create the .github/agents/ directory in your repository
Copy the agent profile (a .md file) into this directory
Commit and push—that's it!
For Hotkey Typer, I copied the WinFormsExpert.agent.md file into .github/agents/, making it immediately available to Copilot throughout my repository.
The Real-World Test: Multiple Snippets Feature
I had opened Issue #3 requesting a new feature: support for managing multiple reusable text snippets instead of just a single predefined block. Users needed to switch between different prompts, code templates, and demo scripts quickly without manual settings editing.
The feature requirements included:
Maintain a list of snippets with names and content
Select active snippet from a dropdown in the main UI
New/Copy/Rename/Delete operations with validation
Backward-compatible migration from the old single-text format
Persist everything in settings.json
First Attempt: Without the Custom Agent (PR #7)
I first assigned the standard Copilot coding agent to Issue #3, resulting in Pull Request #7. The implementation was functional:
✅ Data model created with Snippet class
✅ CRUD operations implemented
✅ UI with ComboBox and management buttons
✅ Unit tests added
✅ Settings serialization worked
However, there were notable issues:
Layout wasn't optimal for WinForms best practices
Controls weren't properly anchored/docked for responsive resizing
Missing some WinForms-specific patterns for dialog handling
Code style didn't fully align with established WinForms conventions
No consideration for DPI scaling or accessibility
While functional, it felt like code written by someone proficient in general C# but not deeply experienced with WinForms specifics.
Second Attempt: With the WinForms Expert Agent (PR #8)
Then I closed PR #7 and reassigned Issue #3 to Copilot, but this time explicitly mentioning the WinForms Expert agent. The results in Pull Request #8 were dramatically better:
The agent reorganized the layout following WinForms best practices:
Logical grouping of snippet management operations
Proper spacing and margins (3px minimum)
Controls properly anchored for responsive behavior
Consideration for DPI scaling
Enhanced User Experience:
Popup dialogs using InputDialog for New/Copy/Rename operations
Proper validation with meaningful error messages
Case-insensitive duplicate detection
Protection against deleting the last snippet
Better visual feedback for operations
Code Quality Improvements:
Modern C# 11-14 features in business logic
Proper separation between designer code and regular code
File-scoped namespaces
Target-typed new expressions
Nullable reference types in appropriate contexts
Exception handling with ArgumentNullException.ThrowIfNull
Even Better Documentation:
The agent included detailed diagrams of the UI layout directly in the PR description, making the implementation immediately understandable.
Key Code Differences: Before and After
Let me show you some concrete examples of the improvements the WinForms Expert agent made:
1. InputDialog Helper Class
PR #7 (Without WinForms Expert): Used an inline dialog creation pattern with repetitive code in each button handler.
PR #8 (With WinForms Expert): Created a dedicated, reusable InputDialog class following WinForms best practices:
internal class InputDialog : Form
{
private readonly TextBox txtInput;
private readonly Button btnOK;
private readonly Button btnCancel;
public string InputText => txtInput.Text;
public InputDialog(string prompt, string title, string defaultValue = "")
{
Text = title;
FormBorderStyle = FormBorderStyle.FixedDialog;
StartPosition = FormStartPosition.CenterParent;
MaximizeBox = false;
MinimizeBox = false;
ClientSize = new Size(350, 120);
// Control setup...
// Select all text when dialog opens
Load += (s, e) => txtInput.SelectAll();
}
public static string? Show(string prompt, string title, string defaultValue = "")
{
using var dialog = new InputDialog(prompt, title, defaultValue);
return dialog.ShowDialog() == DialogResult.OK ? dialog.InputText : null;
}
}
This is a textbook WinForms pattern: proper disposal with using, nullable return type, static factory method, and proper dialog configuration.
2. Settings Migration Pattern
PR #7 (Without WinForms Expert): Used a "fresh start" approach that ignored existing settings if loading failed.
// Migration: Convert old single PredefinedText to snippet if no snippets exist
if (settings.Snippets == null || settings.Snippets.Count == 0)
{
string contentToMigrate = !string.IsNullOrEmpty(settings.PredefinedText)
? settings.PredefinedText
: DefaultSnippetContent;
snippets = new List<TextSnippet>
{
new TextSnippet
{
Id = DefaultSnippetId,
Name = DefaultSnippetName,
Content = contentToMigrate,
LastUsed = DateTime.Now
}
};
activeSnippetId = DefaultSnippetId;
}
else
{
snippets = settings.Snippets;
activeSnippetId = settings.ActiveSnippetId;
// Validate active snippet exists
if (string.IsNullOrEmpty(activeSnippetId) ||
!snippets.Any(s => s.Id == activeSnippetId))
{
activeSnippetId = snippets.FirstOrDefault()?.Id;
}
}
This handles the upgrade path gracefully, preserving user data instead of discarding it.
3. UI Layout in Designer Code
PR #7 (Without WinForms Expert): Controls placed with absolute positioning and cramped spacing:
var btnNewSnippet = new Button
{
Text = "New",
Location = new Point(335, 82),
Size = new Size(35, 25), // Cramped button
Font = new Font("Segoe UI", 8F)
};
PR #8 (With WinForms Expert): Better organized with logical grouping and proper sizing:
// Snippet selector ComboBox - wider, more prominent
var cmbSnippets = new ComboBox
{
Name = "cmbSnippets",
Location = new Point(85, 82),
Size = new Size(300, 23), // 300px width for readability
DropDownStyle = ComboBoxStyle.DropDownList,
Font = new Font("Segoe UI", 9F)
};
// Snippet management buttons - moved under dropdown for logical grouping
var btnNewSnippet = new Button
{
Name = "btnNewSnippet",
Text = "New",
Location = new Point(85, 110),
Size = new Size(70, 25), // Proper button size
Font = new Font("Segoe UI", 9F)
};
The WinForms Expert agent understood that:
ComboBoxes should be wide enough to display meaningful names
Related buttons should be grouped together visually
Font size 8F is too small for modern displays
Controls need descriptive names for maintainability
4. Validation Logic with User Feedback
PR #7: Basic validation without proper user feedback:
if (string.IsNullOrWhiteSpace(name)) return;
if (snippets.Any(s => s.Name.Equals(name, StringComparison.OrdinalIgnoreCase)))
{
MessageBox.Show("A snippet with this name already exists.",
"Duplicate Name", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
PR #8: Enhanced validation with trim and separate empty-string check:
// Validate: trim and check uniqueness (case-insensitive)
name = name.Trim();
if (string.IsNullOrEmpty(name))
{
MessageBox.Show("Snippet name cannot be empty.",
"Invalid Name", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
if (snippets.Any(s => string.Equals(s.Name, name, StringComparison.OrdinalIgnoreCase)))
{
MessageBox.Show("A snippet with this name already exists.",
"Duplicate Name", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
The WinForms Expert understood the importance of:
Trimming user input (common WinForms pattern)
Providing specific error messages for different failure modes
Using string.Equals for clarity over extension method
5. Modern C# Features in Business Logic
PR #8 consistently used modern C# patterns throughout:
// File-scoped namespace
namespace HotkeyTyper;
// Target-typed new
private List<TextSnippet> snippets = new();
// Expression-bodied member
public string InputText => txtInput.Text;
// Nullable reference types
public static string? Show(string prompt, string title, string defaultValue = "")
// Using declaration (proper disposal)
using var dialog = new InputDialog(prompt, title, defaultValue);
While PR #7 had some modern features, PR #8 applied them consistently and appropriately.
6. Comprehensive Unit Tests
PR #8 included thorough migration tests that PR #7 lacked:
[Fact]
public void OldSettings_MigratesToSnippets()
{
// Arrange - Old settings format with single PredefinedText
string oldSettingsJson = """
{
"PredefinedText": "Hello, World!",
"TypingSpeed": 7,
"HasCode": true
}
""";
var oldSettings = JsonSerializer.Deserialize<AppSettings>(oldSettingsJson);
// Act & Assert - Settings should be migrated
Assert.NotNull(oldSettings);
Assert.Null(oldSettings.Snippets);
Assert.Equal("Hello, World!", oldSettings.PredefinedText);
}
These tests ensure the backward compatibility works correctly—a crucial detail for production applications.
The Visible Difference
The before-and-after images tell the story:
Without WinForms Expert:
Basic layout, functional but not polished
Controls not optimally positioned
Missing WinForms-specific refinements
With WinForms Expert:
Clean, logical organization
Professional spacing and alignment
Proper control grouping
Responsive layout that scales correctly
The agent even provided visual diagrams showing the layout structure, demonstrating deep understanding of WinForms design patterns.
The Impact: Quality and Velocity
The difference between PR #7 and PR #8 wasn't just cosmetic. The WinForms Expert agent delivered:
Better Architecture:
Proper use of BindingSource patterns for data binding
Correct designer code serialization patterns
Appropriate use of modern C# features in the right contexts
Following established WinForms conventions consistently
Fewer Iterations:
PR #7 required multiple rounds of feedback and corrections
PR #8 was nearly perfect on the first attempt
Code reviews focused on business logic, not framework usage
Knowledge Transfer:
Seeing the agent's implementation taught me WinForms patterns I hadn't fully mastered
Comments in the code explained why certain approaches were used
The detailed PR descriptions served as documentation
Time Savings:
What would have taken days of iteration took hours
Less time debugging designer code issues
More time focusing on features, not framework quirks
Getting Started with Custom Agents
Ready to try custom agents in your projects? Here's how:
Working with the WinForms Expert agent taught me several valuable lessons:
Specialization Matters: Generic AI coding assistance is good, but specialized agents that understand framework-specific patterns are transformative. The depth of knowledge in the WinForms Expert agent far exceeded what I could convey in ad-hoc prompts.
Consistency Across the Team: Custom agents ensure everyone on your team follows the same patterns and best practices, even for developers less familiar with specific frameworks.
Living Documentation: Agent profiles serve as executable documentation of your team's coding standards—they don't just describe patterns, they enforce them.
Knowledge Amplification: Using specialized agents is a learning experience. Seeing high-quality, pattern-consistent code generated repeatedly helps developers internalize best practices.
Velocity Without Sacrificing Quality: The right custom agent doesn't just generate code faster—it generates better code faster, reducing technical debt and future maintenance burden.
Wrapping Up
The difference between PR #7 (without custom agent) and PR #8 (with WinForms Expert) perfectly illustrates why custom agents represent a significant leap forward in AI-assisted development. It's not about replacing developer expertise—it's about amplifying it with domain-specific knowledge that would otherwise require years of experience to accumulate.
For Hotkey Typer, the WinForms Expert agent helped deliver a feature that not only works but follows industry best practices, is maintainable, accessible, and properly architected for future enhancements. The implementation handles edge cases, uses appropriate design patterns, and would pass a senior code review with minimal comments.
If you're working with WinForms—or any specialized framework—I highly recommend exploring custom agents. The awesome-copilot repository is an excellent starting point, and creating your own team-specific agents is easier than you might think.
Custom agents aren't just the future of AI-assisted development—they're available today, and they're already making a significant difference in how we build software. Give them a try, and see the difference specialized expertise can make in your projects!
Have you tried custom agents in your projects? I'd love to hear about your experiences. Share your thoughts and questions on X!
This blog was written with VS Code and Claude Sonnet 4.5 based on a very lovely prompt with images, MCP server access, and more written by ME! I also reviewed and slightly modified the blog.
The Microsoft Zune is mostly just a footnote in tech history. Microsoft spent years - and vast sums of money - trying to create a true competitor to Apple's iPod, without ever coming close to actually pulling it off. The Zune was simply too little, too late.
You know what's surprising about the Zune, though? Microsoft made a lot of the right bets with the Zune. The company saw - well ahead of most of the rest of the tech industry - that adding social features could make its product stickier. It understood that these pocketable devices might eventually be useful for much more than just music. And it had a bunch of interface design ideas that …